fraim-framework 2.0.73 ā 2.0.75
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 +1 -1
- package/dist/src/cli/commands/init.js +148 -0
- package/dist/src/cli/commands/install.js +86 -0
- package/dist/src/cli/commands/mcp.js +65 -0
- package/dist/src/cli/commands/sync.js +6 -2
- package/dist/src/cli/commands/test-mcp.js +5 -3
- package/dist/src/cli/commands/wizard.js +35 -0
- package/dist/src/cli/fraim.js +2 -0
- package/dist/src/cli/setup/auto-mcp-setup.js +5 -5
- package/dist/src/cli/setup/first-run.js +20 -0
- package/dist/src/cli/setup/ide-detector.js +9 -5
- package/dist/src/cli/setup/mcp-config-generator.js +37 -1
- package/dist/src/cli/utils/remote-sync.js +22 -2
- package/dist/src/core/utils/include-resolver.js +47 -0
- package/index.js +1 -1
- package/package.json +12 -9
package/bin/fraim.js
CHANGED
|
@@ -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));
|
|
@@ -0,0 +1,86 @@
|
|
|
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.installCommand = void 0;
|
|
7
|
+
const commander_1 = require("commander");
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
const child_process_1 = require("child_process");
|
|
10
|
+
const https_1 = __importDefault(require("https"));
|
|
11
|
+
const http_1 = __importDefault(require("http"));
|
|
12
|
+
/**
|
|
13
|
+
* fraim install - One-command installer with key or token
|
|
14
|
+
* Usage: fraim install --key=<key> | fraim install --token=<token> [--api-url=<url>]
|
|
15
|
+
*
|
|
16
|
+
* Convenience wrapper over fraim setup: installs fraim-framework globally, fetches key from
|
|
17
|
+
* token if needed (no copy-paste), then runs fraim setup --key. fraim setup alone requires
|
|
18
|
+
* the user to already have fraim installed and know their key.
|
|
19
|
+
*/
|
|
20
|
+
exports.installCommand = new commander_1.Command('install')
|
|
21
|
+
.description('Install FRAIM globally and configure with your key (use --key or --token from dashboard)')
|
|
22
|
+
.option('--key <key>', 'FRAIM API key (from dashboard)')
|
|
23
|
+
.option('--token <token>', 'Installer token (one-time, from dashboard download link)')
|
|
24
|
+
.option('--api-url <url>', 'API base URL for token fetch', process.env.FRAIM_API_URL || 'https://fraim.wellnessatwork.me')
|
|
25
|
+
.action(async (opts) => {
|
|
26
|
+
let key = opts.key;
|
|
27
|
+
const token = opts.token;
|
|
28
|
+
const apiUrl = (opts.apiUrl || '').replace(/\/$/, '') || 'https://fraim.wellnessatwork.me';
|
|
29
|
+
if (token && !key) {
|
|
30
|
+
console.log(chalk_1.default.blue('Fetching key from FRAIM...'));
|
|
31
|
+
try {
|
|
32
|
+
key = await fetchInstallerKey(token, apiUrl);
|
|
33
|
+
console.log(chalk_1.default.green('Key retrieved.\n'));
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
console.error(chalk_1.default.red('Failed to fetch key:'), err.message);
|
|
37
|
+
console.error(chalk_1.default.gray('Token may be expired. Get a new link from the FRAIM site.'));
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (!key) {
|
|
42
|
+
console.error(chalk_1.default.red('Usage: fraim install --key=<your-key>'));
|
|
43
|
+
console.error(chalk_1.default.gray(' or: fraim install --token=<installer-token> [--api-url=<url>]'));
|
|
44
|
+
console.error(chalk_1.default.gray('\nGet your key at https://fraim.wellnessatwork.me (click Get Started)'));
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
console.log(chalk_1.default.blue('Installing fraim-framework...'));
|
|
48
|
+
const installResult = (0, child_process_1.spawnSync)('npm', ['install', '-g', 'fraim-framework'], {
|
|
49
|
+
stdio: 'inherit',
|
|
50
|
+
shell: true
|
|
51
|
+
});
|
|
52
|
+
if (installResult.status !== 0)
|
|
53
|
+
process.exit(installResult.status || 1);
|
|
54
|
+
console.log(chalk_1.default.blue('\nConfiguring FRAIM with your key...'));
|
|
55
|
+
const setupResult = (0, child_process_1.spawnSync)('fraim', ['setup', '--key', key], {
|
|
56
|
+
stdio: 'inherit',
|
|
57
|
+
shell: true
|
|
58
|
+
});
|
|
59
|
+
if (setupResult.status !== 0)
|
|
60
|
+
process.exit(setupResult.status || 1);
|
|
61
|
+
console.log(chalk_1.default.green('\nā
FRAIM is ready. Run "fraim init-project" in your project directory.'));
|
|
62
|
+
});
|
|
63
|
+
function fetchInstallerKey(token, apiUrl) {
|
|
64
|
+
return new Promise((resolve, reject) => {
|
|
65
|
+
const url = new URL(`${apiUrl}/api/installer-key`);
|
|
66
|
+
url.searchParams.set('token', token);
|
|
67
|
+
const lib = url.protocol === 'https:' ? https_1.default : http_1.default;
|
|
68
|
+
const req = lib.get(url.toString(), (res) => {
|
|
69
|
+
let data = '';
|
|
70
|
+
res.on('data', (chunk) => (data += chunk));
|
|
71
|
+
res.on('end', () => {
|
|
72
|
+
try {
|
|
73
|
+
const json = JSON.parse(data);
|
|
74
|
+
if (json.key)
|
|
75
|
+
resolve(json.key);
|
|
76
|
+
else
|
|
77
|
+
reject(new Error(json.error || 'No key in response'));
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
reject(new Error(`Invalid response: ${data}`));
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
req.on('error', reject);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
@@ -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
|
+
});
|
|
@@ -88,7 +88,7 @@ const runSync = async (options) => {
|
|
|
88
88
|
skipUpdates: true
|
|
89
89
|
});
|
|
90
90
|
if (result.success) {
|
|
91
|
-
console.log(chalk_1.default.green(`ā
Successfully synced ${result.workflowsSynced} workflows
|
|
91
|
+
console.log(chalk_1.default.green(`ā
Successfully synced ${result.workflowsSynced} workflows, ${result.scriptsSynced} scripts, and ${result.coachingSynced} coaching files from local server`));
|
|
92
92
|
return;
|
|
93
93
|
}
|
|
94
94
|
console.error(chalk_1.default.red(`ā Local sync failed: ${result.error}`));
|
|
@@ -113,9 +113,13 @@ 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
|
-
console.log(chalk_1.default.green(`ā
Successfully synced ${result.workflowsSynced} workflows
|
|
122
|
+
console.log(chalk_1.default.green(`ā
Successfully synced ${result.workflowsSynced} workflows, ${result.scriptsSynced} scripts, and ${result.coachingSynced} coaching files from remote`));
|
|
119
123
|
// Update version in config.json
|
|
120
124
|
const configPath = path_1.default.join(fraimDir, 'config.json');
|
|
121
125
|
if (fs_1.default.existsSync(configPath)) {
|
|
@@ -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
|
-
|
|
31
|
+
const servers = ide.configType === 'vscode' ? config.servers : config.mcpServers;
|
|
32
|
+
if (servers) {
|
|
32
33
|
result.configValid = true;
|
|
33
|
-
result.mcpServers = Object.keys(
|
|
34
|
+
result.mcpServers = Object.keys(servers);
|
|
34
35
|
}
|
|
35
36
|
else {
|
|
36
|
-
|
|
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
|
+
});
|
package/dist/src/cli/fraim.js
CHANGED
|
@@ -14,6 +14,7 @@ const test_mcp_1 = require("./commands/test-mcp");
|
|
|
14
14
|
const add_ide_1 = require("./commands/add-ide");
|
|
15
15
|
const override_1 = require("./commands/override");
|
|
16
16
|
const list_overridable_1 = require("./commands/list-overridable");
|
|
17
|
+
const install_1 = require("./commands/install");
|
|
17
18
|
const fs_1 = __importDefault(require("fs"));
|
|
18
19
|
const path_1 = __importDefault(require("path"));
|
|
19
20
|
const program = new commander_1.Command();
|
|
@@ -50,4 +51,5 @@ program.addCommand(test_mcp_1.testMCPCommand);
|
|
|
50
51
|
program.addCommand(add_ide_1.addIDECommand);
|
|
51
52
|
program.addCommand(override_1.overrideCommand);
|
|
52
53
|
program.addCommand(list_overridable_1.listOverridableCommand);
|
|
54
|
+
program.addCommand(install_1.installCommand);
|
|
53
55
|
program.parse(process.argv);
|
|
@@ -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
|
|
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
|
-
|
|
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:
|
|
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: '
|
|
117
|
+
configType: 'vscode',
|
|
114
118
|
detectMethod: detectVSCode,
|
|
115
119
|
alternativePaths: [
|
|
116
|
-
'~/Library/Application Support/Code/User/
|
|
117
|
-
'~/AppData/Roaming/Code/User/
|
|
118
|
-
'~/.config/Code/User/
|
|
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':
|
|
@@ -28,6 +28,7 @@ async function syncFromRemote(options) {
|
|
|
28
28
|
success: false,
|
|
29
29
|
workflowsSynced: 0,
|
|
30
30
|
scriptsSynced: 0,
|
|
31
|
+
coachingSynced: 0,
|
|
31
32
|
error: 'FRAIM_API_KEY not set'
|
|
32
33
|
};
|
|
33
34
|
}
|
|
@@ -48,6 +49,7 @@ async function syncFromRemote(options) {
|
|
|
48
49
|
success: false,
|
|
49
50
|
workflowsSynced: 0,
|
|
50
51
|
scriptsSynced: 0,
|
|
52
|
+
coachingSynced: 0,
|
|
51
53
|
error: 'No files received'
|
|
52
54
|
};
|
|
53
55
|
}
|
|
@@ -88,11 +90,28 @@ async function syncFromRemote(options) {
|
|
|
88
90
|
(0, fs_1.writeFileSync)(filePath, file.content, 'utf8');
|
|
89
91
|
console.log(chalk_1.default.gray(` + ${file.path}`));
|
|
90
92
|
}
|
|
91
|
-
|
|
93
|
+
// Sync coaching files to .fraim/coaching-moments/
|
|
94
|
+
const coachingFiles = files.filter(f => f.type === 'coaching');
|
|
95
|
+
const coachingDir = (0, path_1.join)(options.projectRoot, '.fraim', 'coaching-moments');
|
|
96
|
+
if (!(0, fs_1.existsSync)(coachingDir)) {
|
|
97
|
+
(0, fs_1.mkdirSync)(coachingDir, { recursive: true });
|
|
98
|
+
}
|
|
99
|
+
cleanDirectory(coachingDir);
|
|
100
|
+
for (const file of coachingFiles) {
|
|
101
|
+
const filePath = (0, path_1.join)(coachingDir, file.path);
|
|
102
|
+
const fileDir = (0, path_1.dirname)(filePath);
|
|
103
|
+
if (!(0, fs_1.existsSync)(fileDir)) {
|
|
104
|
+
(0, fs_1.mkdirSync)(fileDir, { recursive: true });
|
|
105
|
+
}
|
|
106
|
+
(0, fs_1.writeFileSync)(filePath, file.content, 'utf8');
|
|
107
|
+
console.log(chalk_1.default.gray(` + coaching-moments/${file.path}`));
|
|
108
|
+
}
|
|
109
|
+
console.log(chalk_1.default.green(`\nā
Synced ${workflowFiles.length} workflows, ${scriptFiles.length} scripts, and ${coachingFiles.length} coaching files from remote`));
|
|
92
110
|
return {
|
|
93
111
|
success: true,
|
|
94
112
|
workflowsSynced: workflowFiles.length,
|
|
95
|
-
scriptsSynced: scriptFiles.length
|
|
113
|
+
scriptsSynced: scriptFiles.length,
|
|
114
|
+
coachingSynced: coachingFiles.length
|
|
96
115
|
};
|
|
97
116
|
}
|
|
98
117
|
catch (error) {
|
|
@@ -101,6 +120,7 @@ async function syncFromRemote(options) {
|
|
|
101
120
|
success: false,
|
|
102
121
|
workflowsSynced: 0,
|
|
103
122
|
scriptsSynced: 0,
|
|
123
|
+
coachingSynced: 0,
|
|
104
124
|
error: error.message
|
|
105
125
|
};
|
|
106
126
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Resolve {{include:path}} directives in registry content.
|
|
4
|
+
*
|
|
5
|
+
* Used by: MCP service (get_fraim_file, get_fraim_workflow), AI Mentor (phase instructions),
|
|
6
|
+
* and any other server-side content that needs includes resolved.
|
|
7
|
+
*
|
|
8
|
+
* - Resolves recursively (e.g. skills can include other skills)
|
|
9
|
+
* - MAX_PASSES prevents infinite loops from circular includes
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.MAX_INCLUDE_PASSES = void 0;
|
|
13
|
+
exports.resolveIncludesWithIndex = resolveIncludesWithIndex;
|
|
14
|
+
const fs_1 = require("fs");
|
|
15
|
+
/** Maximum resolution passes to prevent infinite loops from circular includes */
|
|
16
|
+
exports.MAX_INCLUDE_PASSES = 10;
|
|
17
|
+
/**
|
|
18
|
+
* Resolve {{include:path}} directives in content.
|
|
19
|
+
* Looks up referenced files in the fileIndex and inlines their content.
|
|
20
|
+
*
|
|
21
|
+
* @param content - Raw content that may contain {{include:path}} directives
|
|
22
|
+
* @param fileIndex - Map of path -> { fullPath, ... } for registry files
|
|
23
|
+
* @returns Content with all resolvable includes inlined (recursive, up to MAX_PASSES)
|
|
24
|
+
*/
|
|
25
|
+
function resolveIncludesWithIndex(content, fileIndex) {
|
|
26
|
+
let result = content;
|
|
27
|
+
let pass = 0;
|
|
28
|
+
while (result.includes('{{include:') && pass < exports.MAX_INCLUDE_PASSES) {
|
|
29
|
+
result = result.replace(/\{\{include:([^}]+)\}\}/g, (match, filePath) => {
|
|
30
|
+
const trimmedPath = filePath.trim();
|
|
31
|
+
const fileEntry = fileIndex.get(trimmedPath);
|
|
32
|
+
if (fileEntry?.fullPath) {
|
|
33
|
+
try {
|
|
34
|
+
return (0, fs_1.readFileSync)(fileEntry.fullPath, 'utf-8');
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
console.error(`ā Failed to read included file: ${trimmedPath}`, error);
|
|
38
|
+
return match;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
console.warn(`ā ļø Include file not found in fileIndex: ${trimmedPath}`);
|
|
42
|
+
return match;
|
|
43
|
+
});
|
|
44
|
+
pass++;
|
|
45
|
+
}
|
|
46
|
+
return result;
|
|
47
|
+
}
|
package/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fraim-framework",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.75",
|
|
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": {
|
|
7
7
|
"fraim": "./index.js",
|
|
8
|
+
"fraim-framework": "./index.js",
|
|
8
9
|
"fraim-mcp": "./bin/fraim-mcp.js"
|
|
9
10
|
},
|
|
10
11
|
"scripts": {
|
|
@@ -24,11 +25,12 @@
|
|
|
24
25
|
"postinstall": "fraim sync --skip-updates || echo 'FRAIM setup skipped.'",
|
|
25
26
|
"prepublishOnly": "npm run build",
|
|
26
27
|
"release": "npm version patch && npm publish",
|
|
27
|
-
"test-smoke-ci": "tsx --test tests/test-genericization.ts tests/test-cli.ts tests/test-stub-registry.ts",
|
|
28
|
-
"test-all-ci": "
|
|
29
|
-
"validate:registry": "tsx scripts/verify-registry-paths.ts && npm run validate:workflows && npm run validate:platform-agnostic",
|
|
28
|
+
"test-smoke-ci": "tsx --test tests/test-genericization.ts tests/test-cli.ts tests/test-stub-registry.ts tests/test-include-resolver.ts",
|
|
29
|
+
"test-all-ci": "node scripts/test-with-server.js",
|
|
30
|
+
"validate:registry": "tsx scripts/verify-registry-paths.ts && npm run validate:workflows && npm run validate:skills && npm run validate:platform-agnostic",
|
|
30
31
|
"validate:workflows": "tsx scripts/validate-workflows.ts",
|
|
31
|
-
"validate:platform-agnostic": "tsx scripts/validate-platform-agnostic.ts"
|
|
32
|
+
"validate:platform-agnostic": "tsx scripts/validate-platform-agnostic.ts",
|
|
33
|
+
"validate:skills": "tsx scripts/validate-skills.ts"
|
|
32
34
|
},
|
|
33
35
|
"repository": {
|
|
34
36
|
"type": "git",
|
|
@@ -70,6 +72,7 @@
|
|
|
70
72
|
"html-to-docx": "^1.8.0",
|
|
71
73
|
"markdown-it": "^14.1.0",
|
|
72
74
|
"markdown-it-highlightjs": "^4.2.0",
|
|
75
|
+
"playwright": "^1.58.2",
|
|
73
76
|
"pptxgenjs": "^4.0.1",
|
|
74
77
|
"puppeteer": "^24.36.1",
|
|
75
78
|
"qrcode": "^1.5.4",
|
|
@@ -96,11 +99,11 @@
|
|
|
96
99
|
"axios": "^1.7.0",
|
|
97
100
|
"chalk": "4.1.2",
|
|
98
101
|
"commander": "^14.0.2",
|
|
102
|
+
"cors": "^2.8.5",
|
|
99
103
|
"dotenv": "^16.4.7",
|
|
100
|
-
"prompts": "^2.4.2",
|
|
101
|
-
"tree-kill": "^1.2.2",
|
|
102
104
|
"express": "^5.2.1",
|
|
103
|
-
"
|
|
104
|
-
"
|
|
105
|
+
"mongodb": "^7.0.0",
|
|
106
|
+
"prompts": "^2.4.2",
|
|
107
|
+
"tree-kill": "^1.2.2"
|
|
105
108
|
}
|
|
106
109
|
}
|