fraim-framework 2.0.83 ā 2.0.85
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -3
- package/bin/fraim-mcp.js +1 -1
- package/dist/src/cli/commands/add-ide.js +1 -1
- package/dist/src/cli/commands/init-project.js +1 -1
- package/dist/src/cli/commands/list-overridable.js +19 -15
- package/dist/src/cli/commands/override.js +9 -2
- package/dist/src/cli/commands/setup.js +44 -7
- package/dist/src/cli/commands/sync.js +34 -23
- package/dist/src/cli/doctor/checks/workflow-checks.js +12 -4
- package/dist/src/cli/fraim.js +0 -2
- package/dist/src/cli/mcp/mcp-server-registry.js +2 -0
- package/dist/src/cli/setup/auto-mcp-setup.js +12 -1
- package/dist/src/cli/utils/remote-sync.js +82 -21
- package/dist/src/core/utils/local-registry-resolver.js +171 -14
- package/dist/src/core/utils/stub-generator.js +139 -0
- package/dist/src/core/utils/workflow-parser.js +5 -1
- package/dist/src/local-mcp-server/stdio-server.js +174 -63
- package/index.js +1 -1
- package/package.json +6 -3
package/README.md
CHANGED
|
@@ -228,11 +228,24 @@ R - Retrospectives: Continuous learning from experience
|
|
|
228
228
|
**Why Git Bash on Windows?** All FRAIM scripts use Unix-style paths and Bash commands. Git Bash ensures consistent behavior across platforms.
|
|
229
229
|
|
|
230
230
|
### **Install & Initialize**
|
|
231
|
+
|
|
232
|
+
**Recommended: Use npx (no installation needed)**
|
|
233
|
+
```bash
|
|
234
|
+
npx fraim-framework@latest setup --key=<your-fraim-key>
|
|
235
|
+
|
|
236
|
+
# Optional: Create alias for convenience
|
|
237
|
+
echo 'alias fraim="npx fraim-framework"' >> ~/.bashrc
|
|
238
|
+
source ~/.bashrc
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
**Alternative: Global install**
|
|
231
242
|
```bash
|
|
232
243
|
npm install -g fraim-framework
|
|
233
|
-
fraim setup
|
|
244
|
+
fraim setup --key=<your-fraim-key>
|
|
234
245
|
```
|
|
235
246
|
|
|
247
|
+
> **š” Why npx?** Works with any Node version (16+), no conflicts when switching Node versions, always uses correct dependencies, and identical functionality to global install. Perfect for users with nvm, volta, or multiple Node versions.
|
|
248
|
+
|
|
236
249
|
The setup command supports three modes:
|
|
237
250
|
|
|
238
251
|
**Conversational Mode**: AI workflows only, no platform integration required
|
|
@@ -282,7 +295,7 @@ fraim setup --jira # Add Jira integration
|
|
|
282
295
|
fraim init-project # Initialize FRAIM in current project
|
|
283
296
|
|
|
284
297
|
# Testing and validation
|
|
285
|
-
fraim test-mcp
|
|
298
|
+
fraim doctor --test-mcp # Test MCP server connections
|
|
286
299
|
fraim doctor # Diagnose configuration issues
|
|
287
300
|
|
|
288
301
|
# Sync and maintenance
|
|
@@ -325,7 +338,7 @@ FRAIM uses the official Model Context Protocol (MCP) server for Jira integration
|
|
|
325
338
|
**Troubleshooting**:
|
|
326
339
|
```bash
|
|
327
340
|
# Test Jira MCP connection
|
|
328
|
-
fraim test-mcp
|
|
341
|
+
fraim doctor --test-mcp
|
|
329
342
|
|
|
330
343
|
# Reconfigure Jira integration
|
|
331
344
|
fraim setup --jira
|
package/bin/fraim-mcp.js
CHANGED
|
@@ -418,7 +418,7 @@ const runAddIDE = async (options) => {
|
|
|
418
418
|
console.log(chalk_1.default.blue('\nš Next steps:'));
|
|
419
419
|
console.log(chalk_1.default.cyan(' 1. Restart your configured IDEs'));
|
|
420
420
|
console.log(chalk_1.default.cyan(' 2. Ask your AI agent: "list fraim workflows"'));
|
|
421
|
-
console.log(chalk_1.default.blue('\nš” Use "fraim test-mcp" to verify the configuration.'));
|
|
421
|
+
console.log(chalk_1.default.blue('\nš” Use "fraim doctor --test-mcp" to verify the configuration.'));
|
|
422
422
|
}
|
|
423
423
|
};
|
|
424
424
|
exports.runAddIDE = runAddIDE;
|
|
@@ -159,7 +159,7 @@ const runInitProject = async () => {
|
|
|
159
159
|
fs_1.default.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
160
160
|
console.log(chalk_1.default.green('Created .fraim/config.json'));
|
|
161
161
|
}
|
|
162
|
-
['workflows'].forEach((dir) => {
|
|
162
|
+
['workflows', 'ai-employee/jobs', 'ai-employee/skills', 'ai-manager/jobs', 'personalized-employee'].forEach((dir) => {
|
|
163
163
|
const dirPath = path_1.default.join(fraimDir, dir);
|
|
164
164
|
if (!fs_1.default.existsSync(dirPath)) {
|
|
165
165
|
fs_1.default.mkdirSync(dirPath, { recursive: true });
|
|
@@ -16,7 +16,8 @@ exports.listOverridableCommand = new commander_1.Command('list-overridable')
|
|
|
16
16
|
const projectRoot = process.cwd();
|
|
17
17
|
const fraimDir = path_1.default.join(projectRoot, '.fraim');
|
|
18
18
|
const configPath = path_1.default.join(fraimDir, 'config.json');
|
|
19
|
-
const
|
|
19
|
+
const personalizedDir = path_1.default.join(fraimDir, 'personalized-employee');
|
|
20
|
+
const legacyOverridesDir = path_1.default.join(fraimDir, 'overrides');
|
|
20
21
|
// Validate .fraim directory exists
|
|
21
22
|
if (!fs_1.default.existsSync(fraimDir)) {
|
|
22
23
|
console.log(chalk_1.default.red('ā .fraim/ directory not found. Run "fraim setup" or "fraim init-project" first.'));
|
|
@@ -42,20 +43,23 @@ exports.listOverridableCommand = new commander_1.Command('list-overridable')
|
|
|
42
43
|
console.log(chalk_1.default.blue('š Overridable FRAIM Registry Paths:\n'));
|
|
43
44
|
// Get list of existing overrides
|
|
44
45
|
const existingOverrides = new Set();
|
|
45
|
-
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
scanDir(path_1.default.join(dir, entry.name), relativePath);
|
|
52
|
-
}
|
|
53
|
-
else {
|
|
54
|
-
existingOverrides.add(relativePath.replace(/\\/g, '/'));
|
|
55
|
-
}
|
|
46
|
+
const scanDir = (dir, base = '') => {
|
|
47
|
+
const entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
|
|
48
|
+
for (const entry of entries) {
|
|
49
|
+
const relativePath = path_1.default.join(base, entry.name);
|
|
50
|
+
if (entry.isDirectory()) {
|
|
51
|
+
scanDir(path_1.default.join(dir, entry.name), relativePath);
|
|
56
52
|
}
|
|
57
|
-
|
|
58
|
-
|
|
53
|
+
else {
|
|
54
|
+
existingOverrides.add(relativePath.replace(/\\/g, '/'));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
if (fs_1.default.existsSync(personalizedDir)) {
|
|
59
|
+
scanDir(personalizedDir);
|
|
60
|
+
}
|
|
61
|
+
if (fs_1.default.existsSync(legacyOverridesDir)) {
|
|
62
|
+
scanDir(legacyOverridesDir);
|
|
59
63
|
}
|
|
60
64
|
// Handle --rules flag
|
|
61
65
|
if (options.rules) {
|
|
@@ -197,6 +201,6 @@ exports.listOverridableCommand = new commander_1.Command('list-overridable')
|
|
|
197
201
|
console.log(chalk_1.default.gray(' ⢠Use "fraim override <path> --copy" to copy current content'));
|
|
198
202
|
console.log(chalk_1.default.gray(' ⢠Use --job-category <category> to see category-specific items'));
|
|
199
203
|
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/
|
|
204
|
+
console.log(chalk_1.default.gray(' ⢠Overrides are stored in .fraim/personalized-employee/'));
|
|
201
205
|
}
|
|
202
206
|
});
|
|
@@ -39,8 +39,8 @@ exports.overrideCommand = new commander_1.Command('override')
|
|
|
39
39
|
console.log(chalk_1.default.red('ā Must specify either --inherit or --copy.'));
|
|
40
40
|
process.exit(1);
|
|
41
41
|
}
|
|
42
|
-
// Create
|
|
43
|
-
const overridePath = path_1.default.join(fraimDir, '
|
|
42
|
+
// Create personalized override directory structure
|
|
43
|
+
const overridePath = path_1.default.join(fraimDir, 'personalized-employee', registryPath);
|
|
44
44
|
const overrideDir = path_1.default.dirname(overridePath);
|
|
45
45
|
if (!fs_1.default.existsSync(overrideDir)) {
|
|
46
46
|
fs_1.default.mkdirSync(overrideDir, { recursive: true });
|
|
@@ -124,6 +124,13 @@ exports.overrideCommand = new commander_1.Command('override')
|
|
|
124
124
|
toolName = 'get_fraim_workflow';
|
|
125
125
|
toolArgs = { workflow: workflowName };
|
|
126
126
|
}
|
|
127
|
+
else if (registryPath.startsWith('jobs/')) {
|
|
128
|
+
// e.g., "jobs/product-building/feature-specification.md" -> "feature-specification"
|
|
129
|
+
const parts = registryPath.split('/');
|
|
130
|
+
const jobName = parts[parts.length - 1].replace('.md', '');
|
|
131
|
+
toolName = 'get_fraim_job';
|
|
132
|
+
toolArgs = { job: jobName };
|
|
133
|
+
}
|
|
127
134
|
else {
|
|
128
135
|
toolName = 'get_fraim_file';
|
|
129
136
|
toolArgs = { path: registryPath };
|
|
@@ -240,16 +240,20 @@ const runSetup = async (options) => {
|
|
|
240
240
|
console.log(chalk_1.default.blue('š Updating existing FRAIM configuration...\n'));
|
|
241
241
|
try {
|
|
242
242
|
const existingConfig = JSON.parse(fs_1.default.readFileSync(globalConfigPath, 'utf8'));
|
|
243
|
-
|
|
243
|
+
// Allow updating FRAIM key even without provider changes
|
|
244
|
+
fraimKey = options.key || existingConfig.apiKey;
|
|
244
245
|
// Now we can parse options with the FRAIM key
|
|
245
246
|
const parsed = await parseLegacyOptions(options, fraimKey);
|
|
246
247
|
requestedProviders = parsed.requestedProviders;
|
|
247
248
|
providedTokens = parsed.providedTokens;
|
|
248
249
|
providedConfigs = parsed.providedConfigs;
|
|
249
|
-
//
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
250
|
+
// Check if this is just a key update or a provider update
|
|
251
|
+
const isKeyUpdate = options.key && options.key !== existingConfig.apiKey;
|
|
252
|
+
const isProviderUpdate = requestedProviders.length > 0;
|
|
253
|
+
// Only proceed if there's something to update
|
|
254
|
+
if (!isKeyUpdate && !isProviderUpdate) {
|
|
255
|
+
console.log(chalk_1.default.yellow('ā ļø No changes specified.'));
|
|
256
|
+
console.log(chalk_1.default.gray('Use --key to update FRAIM key, or --github, --gitlab, etc. to add providers.'));
|
|
253
257
|
return;
|
|
254
258
|
}
|
|
255
259
|
mode = existingConfig.mode || 'integrated';
|
|
@@ -377,8 +381,9 @@ const runSetup = async (options) => {
|
|
|
377
381
|
// Save global configuration
|
|
378
382
|
console.log(chalk_1.default.blue('š¾ Saving global configuration...'));
|
|
379
383
|
saveGlobalConfig(fraimKey, mode, tokens, configs);
|
|
380
|
-
// Configure MCP servers
|
|
384
|
+
// Configure MCP servers
|
|
381
385
|
if (!isUpdate) {
|
|
386
|
+
// Initial setup - configure all detected IDEs
|
|
382
387
|
console.log(chalk_1.default.blue('\nš Configuring MCP servers...'));
|
|
383
388
|
// Convert to legacy format for MCP config generator
|
|
384
389
|
const mcpTokens = {};
|
|
@@ -401,7 +406,39 @@ const runSetup = async (options) => {
|
|
|
401
406
|
console.log(chalk_1.default.yellow('ā ļø MCP configuration encountered issues'));
|
|
402
407
|
console.log(chalk_1.default.gray(' You can configure MCP manually or run setup again later\n'));
|
|
403
408
|
}
|
|
404
|
-
|
|
409
|
+
}
|
|
410
|
+
else {
|
|
411
|
+
// Update existing setup - refresh all IDE MCP configs with new keys
|
|
412
|
+
console.log(chalk_1.default.blue('\nš Updating IDE MCP configurations...'));
|
|
413
|
+
try {
|
|
414
|
+
const { detectInstalledIDEs } = await Promise.resolve().then(() => __importStar(require('../setup/ide-detector')));
|
|
415
|
+
const installedIDEs = detectInstalledIDEs();
|
|
416
|
+
if (installedIDEs.length === 0) {
|
|
417
|
+
console.log(chalk_1.default.gray(' No IDE configurations found to update'));
|
|
418
|
+
}
|
|
419
|
+
else {
|
|
420
|
+
// Convert to legacy format for MCP config generator
|
|
421
|
+
const mcpTokens = {};
|
|
422
|
+
Object.entries(tokens).forEach(([id, token]) => {
|
|
423
|
+
mcpTokens[id] = token;
|
|
424
|
+
});
|
|
425
|
+
// Build providerConfigs map from configs
|
|
426
|
+
const providerConfigsMap = {};
|
|
427
|
+
Object.entries(configs).forEach(([providerId, config]) => {
|
|
428
|
+
providerConfigsMap[providerId] = config;
|
|
429
|
+
});
|
|
430
|
+
const ideNames = installedIDEs.map(ide => ide.name);
|
|
431
|
+
await (0, auto_mcp_setup_1.autoConfigureMCP)(fraimKey, mcpTokens, ideNames, providerConfigsMap);
|
|
432
|
+
console.log(chalk_1.default.green(`ā
Updated MCP configs for: ${ideNames.join(', ')}`));
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
catch (e) {
|
|
436
|
+
console.log(chalk_1.default.yellow('ā ļø Failed to update IDE MCP configurations'));
|
|
437
|
+
console.log(chalk_1.default.gray(' You can update them manually with: fraim add-ide <ide-name>\n'));
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
// Auto-run project init if we're in a git repo (only on initial setup)
|
|
441
|
+
if (!isUpdate) {
|
|
405
442
|
if ((0, platform_detection_1.isGitRepository)()) {
|
|
406
443
|
console.log(chalk_1.default.blue('\nš Git repository detected ā initializing project...'));
|
|
407
444
|
await (0, init_project_1.runInitProject)();
|
|
@@ -63,6 +63,24 @@ function loadUserApiKey() {
|
|
|
63
63
|
return undefined;
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
|
+
function updateVersionInConfig(fraimDir) {
|
|
67
|
+
const configPath = path_1.default.join(fraimDir, 'config.json');
|
|
68
|
+
if (!fs_1.default.existsSync(configPath)) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
const currentConfig = JSON.parse(fs_1.default.readFileSync(configPath, 'utf8'));
|
|
73
|
+
const newVersion = (0, version_utils_1.getFraimVersion)();
|
|
74
|
+
if (currentConfig.version !== newVersion) {
|
|
75
|
+
currentConfig.version = newVersion;
|
|
76
|
+
fs_1.default.writeFileSync(configPath, JSON.stringify(currentConfig, null, 2));
|
|
77
|
+
console.log(chalk_1.default.green(`ā
Updated FRAIM version to ${newVersion} in config.`));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch (e) {
|
|
81
|
+
console.warn(chalk_1.default.yellow('ā ļø Could not update version in config.json.'));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
66
84
|
const runSync = async (options) => {
|
|
67
85
|
const projectRoot = process.cwd();
|
|
68
86
|
const config = (0, config_loader_1.loadFraimConfig)();
|
|
@@ -88,7 +106,8 @@ const runSync = async (options) => {
|
|
|
88
106
|
skipUpdates: true
|
|
89
107
|
});
|
|
90
108
|
if (result.success) {
|
|
91
|
-
console.log(chalk_1.default.green(`ā
Successfully synced ${result.workflowsSynced} workflows, ${result.
|
|
109
|
+
console.log(chalk_1.default.green(`ā
Successfully synced ${result.workflowsSynced} workflows, ${result.employeeJobsSynced} ai-employee jobs, ${result.managerJobsSynced} ai-manager jobs, ${result.skillsSynced} skills, ${result.rulesSynced} rules, ${result.scriptsSynced} scripts, and ${result.docsSynced} docs from local server`));
|
|
110
|
+
updateVersionInConfig(fraimDir);
|
|
92
111
|
return;
|
|
93
112
|
}
|
|
94
113
|
console.error(chalk_1.default.red(`ā Local sync failed: ${result.error}`));
|
|
@@ -96,12 +115,18 @@ const runSync = async (options) => {
|
|
|
96
115
|
process.exit(1);
|
|
97
116
|
}
|
|
98
117
|
// Path 2: Remote sync with API key
|
|
99
|
-
|
|
118
|
+
let apiKey = loadUserApiKey() || config.apiKey || process.env.FRAIM_API_KEY;
|
|
100
119
|
if (!apiKey) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
120
|
+
if (process.env.TEST_MODE === 'true') {
|
|
121
|
+
console.log(chalk_1.default.yellow('ā ļø TEST_MODE: No API key configured. Using test placeholder key.'));
|
|
122
|
+
apiKey = 'test-mode-key';
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
console.error(chalk_1.default.red('ā No API key configured. Cannot sync.'));
|
|
126
|
+
console.error(chalk_1.default.yellow('š” Set FRAIM_API_KEY in your environment, or add apiKey to ~/.fraim/config.json or .fraim/config.json'));
|
|
127
|
+
console.error(chalk_1.default.yellow('š” Or use --local to sync from a locally running FRAIM server.'));
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
105
130
|
}
|
|
106
131
|
console.log(chalk_1.default.blue('š Syncing FRAIM workflows from remote server...'));
|
|
107
132
|
const result = await syncFromRemote({
|
|
@@ -115,27 +140,13 @@ const runSync = async (options) => {
|
|
|
115
140
|
console.error(chalk_1.default.yellow('š” Check your API key and network connection.'));
|
|
116
141
|
if (process.env.TEST_MODE === 'true') {
|
|
117
142
|
console.log(chalk_1.default.yellow('ā ļø TEST_MODE: Continuing without remote sync (server may be unavailable).'));
|
|
143
|
+
updateVersionInConfig(fraimDir);
|
|
118
144
|
return;
|
|
119
145
|
}
|
|
120
146
|
process.exit(1);
|
|
121
147
|
}
|
|
122
|
-
console.log(chalk_1.default.green(`ā
Successfully synced ${result.workflowsSynced} workflows, ${result.
|
|
123
|
-
|
|
124
|
-
const configPath = path_1.default.join(fraimDir, 'config.json');
|
|
125
|
-
if (fs_1.default.existsSync(configPath)) {
|
|
126
|
-
try {
|
|
127
|
-
const currentConfig = JSON.parse(fs_1.default.readFileSync(configPath, 'utf8'));
|
|
128
|
-
const newVersion = (0, version_utils_1.getFraimVersion)();
|
|
129
|
-
if (currentConfig.version !== newVersion) {
|
|
130
|
-
currentConfig.version = newVersion;
|
|
131
|
-
fs_1.default.writeFileSync(configPath, JSON.stringify(currentConfig, null, 2));
|
|
132
|
-
console.log(chalk_1.default.green(`ā
Updated FRAIM version to ${newVersion} in config.`));
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
catch (e) {
|
|
136
|
-
console.warn(chalk_1.default.yellow('ā ļø Could not update version in config.json.'));
|
|
137
|
-
}
|
|
138
|
-
}
|
|
148
|
+
console.log(chalk_1.default.green(`ā
Successfully synced ${result.workflowsSynced} workflows, ${result.employeeJobsSynced} ai-employee jobs, ${result.managerJobsSynced} ai-manager jobs, ${result.skillsSynced} skills, ${result.rulesSynced} rules, ${result.scriptsSynced} scripts, and ${result.docsSynced} docs from remote`));
|
|
149
|
+
updateVersionInConfig(fraimDir);
|
|
139
150
|
};
|
|
140
151
|
exports.runSync = runSync;
|
|
141
152
|
async function checkAndUpdateCLI() {
|
|
@@ -166,8 +166,9 @@ function checkOverrideSyntaxValid() {
|
|
|
166
166
|
category: 'workflows',
|
|
167
167
|
critical: false,
|
|
168
168
|
run: async () => {
|
|
169
|
-
const
|
|
170
|
-
|
|
169
|
+
const personalizedDir = path_1.default.join(process.cwd(), '.fraim', 'personalized-employee');
|
|
170
|
+
const legacyOverridesDir = path_1.default.join(process.cwd(), '.fraim', 'overrides');
|
|
171
|
+
if (!fs_1.default.existsSync(personalizedDir) && !fs_1.default.existsSync(legacyOverridesDir)) {
|
|
171
172
|
return {
|
|
172
173
|
status: 'passed',
|
|
173
174
|
message: 'No overrides (not required)',
|
|
@@ -192,10 +193,17 @@ function checkOverrideSyntaxValid() {
|
|
|
192
193
|
}
|
|
193
194
|
}
|
|
194
195
|
};
|
|
195
|
-
|
|
196
|
+
if (fs_1.default.existsSync(personalizedDir)) {
|
|
197
|
+
scanDir(personalizedDir);
|
|
198
|
+
}
|
|
199
|
+
if (fs_1.default.existsSync(legacyOverridesDir)) {
|
|
200
|
+
scanDir(legacyOverridesDir);
|
|
201
|
+
}
|
|
196
202
|
// Validate each override
|
|
197
203
|
for (const override of overrides) {
|
|
198
|
-
const
|
|
204
|
+
const primaryPath = path_1.default.join(personalizedDir, override);
|
|
205
|
+
const legacyPath = path_1.default.join(legacyOverridesDir, override);
|
|
206
|
+
const overridePath = fs_1.default.existsSync(primaryPath) ? primaryPath : legacyPath;
|
|
199
207
|
const content = fs_1.default.readFileSync(overridePath, 'utf-8');
|
|
200
208
|
const parseResult = parser.parse(content);
|
|
201
209
|
if (parseResult.hasImports) {
|
package/dist/src/cli/fraim.js
CHANGED
|
@@ -43,7 +43,6 @@ const doctor_1 = require("./commands/doctor");
|
|
|
43
43
|
const list_1 = require("./commands/list");
|
|
44
44
|
const setup_1 = require("./commands/setup");
|
|
45
45
|
const init_project_1 = require("./commands/init-project");
|
|
46
|
-
const test_mcp_1 = require("./commands/test-mcp");
|
|
47
46
|
const add_ide_1 = require("./commands/add-ide");
|
|
48
47
|
const add_provider_1 = require("./commands/add-provider");
|
|
49
48
|
const override_1 = require("./commands/override");
|
|
@@ -80,7 +79,6 @@ program.addCommand(doctor_1.doctorCommand);
|
|
|
80
79
|
program.addCommand(list_1.listCommand);
|
|
81
80
|
program.addCommand(setup_1.setupCommand);
|
|
82
81
|
program.addCommand(init_project_1.initProjectCommand);
|
|
83
|
-
program.addCommand(test_mcp_1.testMCPCommand);
|
|
84
82
|
program.addCommand(add_ide_1.addIDECommand);
|
|
85
83
|
program.addCommand(add_provider_1.addProviderCommand);
|
|
86
84
|
program.addCommand(override_1.overrideCommand);
|
|
@@ -38,6 +38,8 @@ exports.BASE_MCP_SERVERS = [
|
|
|
38
38
|
buildServer: (fraimKey) => ({
|
|
39
39
|
command: 'fraim-mcp',
|
|
40
40
|
env: {
|
|
41
|
+
// Include API key for IDE configs (Codex, VSCode, etc.)
|
|
42
|
+
// The stdio-server will use this if set, otherwise falls back to ~/.fraim/config.json
|
|
41
43
|
FRAIM_API_KEY: fraimKey,
|
|
42
44
|
FRAIM_REMOTE_URL: 'https://fraim.wellnessatwork.me'
|
|
43
45
|
}
|
|
@@ -203,12 +203,20 @@ const configureIDEMCP = async (ide, fraimKey, tokenInput, providerConfigs) => {
|
|
|
203
203
|
// Merge MCP servers intelligently
|
|
204
204
|
const mergedMCPServers = { ...existingMCPServers };
|
|
205
205
|
const addedServers = [];
|
|
206
|
+
const updatedServers = [];
|
|
206
207
|
const skippedServers = [];
|
|
208
|
+
// Servers that should always be updated (not skipped)
|
|
209
|
+
const alwaysUpdateServers = new Set(['fraim', 'github', 'gitlab', 'jira', 'ado', 'linear']);
|
|
207
210
|
for (const [serverName, serverConfig] of Object.entries(newMCPServers)) {
|
|
208
211
|
if (!existingMCPServers[serverName]) {
|
|
209
212
|
mergedMCPServers[serverName] = serverConfig;
|
|
210
213
|
addedServers.push(serverName);
|
|
211
214
|
}
|
|
215
|
+
else if (alwaysUpdateServers.has(serverName)) {
|
|
216
|
+
// Always update these servers to ensure keys/tokens are current
|
|
217
|
+
mergedMCPServers[serverName] = serverConfig;
|
|
218
|
+
updatedServers.push(serverName);
|
|
219
|
+
}
|
|
212
220
|
else {
|
|
213
221
|
skippedServers.push(serverName);
|
|
214
222
|
}
|
|
@@ -223,8 +231,11 @@ const configureIDEMCP = async (ide, fraimKey, tokenInput, providerConfigs) => {
|
|
|
223
231
|
addedServers.forEach(server => {
|
|
224
232
|
console.log(chalk_1.default.green(` ā
Added ${server} MCP server`));
|
|
225
233
|
});
|
|
234
|
+
updatedServers.forEach(server => {
|
|
235
|
+
console.log(chalk_1.default.blue(` š Updated ${server} MCP server`));
|
|
236
|
+
});
|
|
226
237
|
skippedServers.forEach(server => {
|
|
227
|
-
console.log(chalk_1.default.gray(` āļø
|
|
238
|
+
console.log(chalk_1.default.gray(` āļø Skipped ${server} (already exists)`));
|
|
228
239
|
});
|
|
229
240
|
}
|
|
230
241
|
console.log(chalk_1.default.green(`ā
Updated ${configPath}`));
|
|
@@ -27,8 +27,11 @@ async function syncFromRemote(options) {
|
|
|
27
27
|
return {
|
|
28
28
|
success: false,
|
|
29
29
|
workflowsSynced: 0,
|
|
30
|
+
employeeJobsSynced: 0,
|
|
31
|
+
managerJobsSynced: 0,
|
|
32
|
+
skillsSynced: 0,
|
|
33
|
+
rulesSynced: 0,
|
|
30
34
|
scriptsSynced: 0,
|
|
31
|
-
coachingSynced: 0,
|
|
32
35
|
docsSynced: 0,
|
|
33
36
|
error: 'FRAIM_API_KEY not set'
|
|
34
37
|
};
|
|
@@ -49,8 +52,11 @@ async function syncFromRemote(options) {
|
|
|
49
52
|
return {
|
|
50
53
|
success: false,
|
|
51
54
|
workflowsSynced: 0,
|
|
55
|
+
employeeJobsSynced: 0,
|
|
56
|
+
managerJobsSynced: 0,
|
|
57
|
+
skillsSynced: 0,
|
|
58
|
+
rulesSynced: 0,
|
|
52
59
|
scriptsSynced: 0,
|
|
53
|
-
coachingSynced: 0,
|
|
54
60
|
docsSynced: 0,
|
|
55
61
|
error: 'No files received'
|
|
56
62
|
};
|
|
@@ -73,6 +79,72 @@ async function syncFromRemote(options) {
|
|
|
73
79
|
(0, fs_1.writeFileSync)(filePath, file.content, 'utf8');
|
|
74
80
|
console.log(chalk_1.default.gray(` + ${file.path}`));
|
|
75
81
|
}
|
|
82
|
+
// Sync job stubs to role-specific folders under .fraim
|
|
83
|
+
const allJobFiles = files.filter(f => f.type === 'job');
|
|
84
|
+
const managerJobFiles = allJobFiles.filter(f => f.path.startsWith('ai-manager/'));
|
|
85
|
+
const jobFiles = allJobFiles.filter(f => !f.path.startsWith('ai-manager/'));
|
|
86
|
+
const employeeJobsDir = (0, path_1.join)(options.projectRoot, '.fraim', 'ai-employee', 'jobs');
|
|
87
|
+
if (!(0, fs_1.existsSync)(employeeJobsDir)) {
|
|
88
|
+
(0, fs_1.mkdirSync)(employeeJobsDir, { recursive: true });
|
|
89
|
+
}
|
|
90
|
+
cleanDirectory(employeeJobsDir);
|
|
91
|
+
for (const file of jobFiles) {
|
|
92
|
+
const filePath = (0, path_1.join)(employeeJobsDir, file.path);
|
|
93
|
+
const fileDir = (0, path_1.dirname)(filePath);
|
|
94
|
+
if (!(0, fs_1.existsSync)(fileDir)) {
|
|
95
|
+
(0, fs_1.mkdirSync)(fileDir, { recursive: true });
|
|
96
|
+
}
|
|
97
|
+
(0, fs_1.writeFileSync)(filePath, file.content, 'utf8');
|
|
98
|
+
console.log(chalk_1.default.gray(` + ai-employee/jobs/${file.path}`));
|
|
99
|
+
}
|
|
100
|
+
// Sync ai-manager job stubs to .fraim/ai-manager/jobs/
|
|
101
|
+
const managerJobsDir = (0, path_1.join)(options.projectRoot, '.fraim', 'ai-manager', 'jobs');
|
|
102
|
+
if (!(0, fs_1.existsSync)(managerJobsDir)) {
|
|
103
|
+
(0, fs_1.mkdirSync)(managerJobsDir, { recursive: true });
|
|
104
|
+
}
|
|
105
|
+
cleanDirectory(managerJobsDir);
|
|
106
|
+
for (const file of managerJobFiles) {
|
|
107
|
+
const managerRelativePath = file.path.replace(/^ai-manager\//, '');
|
|
108
|
+
const filePath = (0, path_1.join)(managerJobsDir, managerRelativePath);
|
|
109
|
+
const fileDir = (0, path_1.dirname)(filePath);
|
|
110
|
+
if (!(0, fs_1.existsSync)(fileDir)) {
|
|
111
|
+
(0, fs_1.mkdirSync)(fileDir, { recursive: true });
|
|
112
|
+
}
|
|
113
|
+
(0, fs_1.writeFileSync)(filePath, file.content, 'utf8');
|
|
114
|
+
console.log(chalk_1.default.gray(` + ai-manager/jobs/${managerRelativePath}`));
|
|
115
|
+
}
|
|
116
|
+
// Sync full skill files to .fraim/ai-employee/skills/
|
|
117
|
+
const skillFiles = files.filter(f => f.type === 'skill');
|
|
118
|
+
const skillsDir = (0, path_1.join)(options.projectRoot, '.fraim', 'ai-employee', 'skills');
|
|
119
|
+
if (!(0, fs_1.existsSync)(skillsDir)) {
|
|
120
|
+
(0, fs_1.mkdirSync)(skillsDir, { recursive: true });
|
|
121
|
+
}
|
|
122
|
+
cleanDirectory(skillsDir);
|
|
123
|
+
for (const file of skillFiles) {
|
|
124
|
+
const filePath = (0, path_1.join)(skillsDir, file.path);
|
|
125
|
+
const fileDir = (0, path_1.dirname)(filePath);
|
|
126
|
+
if (!(0, fs_1.existsSync)(fileDir)) {
|
|
127
|
+
(0, fs_1.mkdirSync)(fileDir, { recursive: true });
|
|
128
|
+
}
|
|
129
|
+
(0, fs_1.writeFileSync)(filePath, file.content, 'utf8');
|
|
130
|
+
console.log(chalk_1.default.gray(` + ai-employee/skills/${file.path}`));
|
|
131
|
+
}
|
|
132
|
+
// Sync full rule files to .fraim/ai-employee/rules/
|
|
133
|
+
const ruleFiles = files.filter(f => f.type === 'rule');
|
|
134
|
+
const rulesDir = (0, path_1.join)(options.projectRoot, '.fraim', 'ai-employee', 'rules');
|
|
135
|
+
if (!(0, fs_1.existsSync)(rulesDir)) {
|
|
136
|
+
(0, fs_1.mkdirSync)(rulesDir, { recursive: true });
|
|
137
|
+
}
|
|
138
|
+
cleanDirectory(rulesDir);
|
|
139
|
+
for (const file of ruleFiles) {
|
|
140
|
+
const filePath = (0, path_1.join)(rulesDir, file.path);
|
|
141
|
+
const fileDir = (0, path_1.dirname)(filePath);
|
|
142
|
+
if (!(0, fs_1.existsSync)(fileDir)) {
|
|
143
|
+
(0, fs_1.mkdirSync)(fileDir, { recursive: true });
|
|
144
|
+
}
|
|
145
|
+
(0, fs_1.writeFileSync)(filePath, file.content, 'utf8');
|
|
146
|
+
console.log(chalk_1.default.gray(` + ai-employee/rules/${file.path}`));
|
|
147
|
+
}
|
|
76
148
|
// Sync scripts to user directory
|
|
77
149
|
const scriptFiles = files.filter(f => f.type === 'script');
|
|
78
150
|
const userDir = (0, script_sync_utils_1.getUserFraimDir)();
|
|
@@ -92,22 +164,6 @@ async function syncFromRemote(options) {
|
|
|
92
164
|
(0, fs_1.writeFileSync)(filePath, file.content, 'utf8');
|
|
93
165
|
console.log(chalk_1.default.gray(` + ${file.path}`));
|
|
94
166
|
}
|
|
95
|
-
// Sync coaching files to .fraim/coaching-moments/
|
|
96
|
-
const coachingFiles = files.filter(f => f.type === 'coaching');
|
|
97
|
-
const coachingDir = (0, path_1.join)(options.projectRoot, '.fraim', 'coaching-moments');
|
|
98
|
-
if (!(0, fs_1.existsSync)(coachingDir)) {
|
|
99
|
-
(0, fs_1.mkdirSync)(coachingDir, { recursive: true });
|
|
100
|
-
}
|
|
101
|
-
cleanDirectory(coachingDir);
|
|
102
|
-
for (const file of coachingFiles) {
|
|
103
|
-
const filePath = (0, path_1.join)(coachingDir, file.path);
|
|
104
|
-
const fileDir = (0, path_1.dirname)(filePath);
|
|
105
|
-
if (!(0, fs_1.existsSync)(fileDir)) {
|
|
106
|
-
(0, fs_1.mkdirSync)(fileDir, { recursive: true });
|
|
107
|
-
}
|
|
108
|
-
(0, fs_1.writeFileSync)(filePath, file.content, 'utf8');
|
|
109
|
-
console.log(chalk_1.default.gray(` + coaching-moments/${file.path}`));
|
|
110
|
-
}
|
|
111
167
|
// Sync docs to .fraim/docs/
|
|
112
168
|
const docsFiles = files.filter(f => f.type === 'docs');
|
|
113
169
|
const docsDir = (0, path_1.join)(options.projectRoot, '.fraim', 'docs');
|
|
@@ -124,12 +180,14 @@ async function syncFromRemote(options) {
|
|
|
124
180
|
(0, fs_1.writeFileSync)(filePath, file.content, 'utf8');
|
|
125
181
|
console.log(chalk_1.default.gray(` + docs/${file.path}`));
|
|
126
182
|
}
|
|
127
|
-
console.log(chalk_1.default.green(`\nā
Synced ${workflowFiles.length} workflows, ${scriptFiles.length} scripts, ${coachingFiles.length} coaching files, and ${docsFiles.length} docs from remote`));
|
|
128
183
|
return {
|
|
129
184
|
success: true,
|
|
130
185
|
workflowsSynced: workflowFiles.length,
|
|
186
|
+
employeeJobsSynced: jobFiles.length,
|
|
187
|
+
managerJobsSynced: managerJobFiles.length,
|
|
188
|
+
skillsSynced: skillFiles.length,
|
|
189
|
+
rulesSynced: ruleFiles.length,
|
|
131
190
|
scriptsSynced: scriptFiles.length,
|
|
132
|
-
coachingSynced: coachingFiles.length,
|
|
133
191
|
docsSynced: docsFiles.length
|
|
134
192
|
};
|
|
135
193
|
}
|
|
@@ -138,8 +196,11 @@ async function syncFromRemote(options) {
|
|
|
138
196
|
return {
|
|
139
197
|
success: false,
|
|
140
198
|
workflowsSynced: 0,
|
|
199
|
+
employeeJobsSynced: 0,
|
|
200
|
+
managerJobsSynced: 0,
|
|
201
|
+
skillsSynced: 0,
|
|
202
|
+
rulesSynced: 0,
|
|
141
203
|
scriptsSynced: 0,
|
|
142
|
-
coachingSynced: 0,
|
|
143
204
|
docsSynced: 0,
|
|
144
205
|
error: error.message
|
|
145
206
|
};
|