fraim 2.0.159 → 2.0.160
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 +1 -1
- package/dist/src/ai-hub/cert-store.js +70 -0
- package/dist/src/ai-hub/desktop-main.js +225 -50
- package/dist/src/ai-hub/manager-turns.js +38 -0
- package/dist/src/ai-hub/office-sideload.js +138 -0
- package/dist/src/ai-hub/openclaw-bridge.js +239 -0
- package/dist/src/ai-hub/server.js +346 -115
- package/dist/src/ai-hub/word-sideload.js +95 -0
- package/dist/src/cli/commands/add-ide.js +9 -0
- package/dist/src/cli/commands/login.js +1 -2
- package/dist/src/cli/commands/setup.js +0 -2
- package/dist/src/cli/commands/sync.js +19 -10
- package/dist/src/cli/doctor/checks/mcp-connectivity-checks.js +66 -2
- package/dist/src/cli/doctor/checks/workflow-checks.js +1 -65
- package/dist/src/cli/mcp/fraim-mcp-latest-launcher.js +136 -0
- package/dist/src/cli/mcp/mcp-server-registry.js +14 -10
- package/dist/src/cli/setup/auto-mcp-setup.js +1 -1
- package/dist/src/cli/utils/fraim-gitignore.js +11 -0
- package/dist/src/cli/utils/remote-sync.js +1 -1
- package/dist/src/core/config-loader.js +1 -2
- package/dist/src/core/fraim-config-schema.generated.js +0 -5
- package/dist/src/core/types.js +0 -1
- package/dist/src/first-run/session-service.js +3 -3
- package/package.json +2 -1
- package/public/ai-hub/index.html +20 -2
- package/public/ai-hub/powerpoint-taskpane/icon-64.png +0 -0
- package/public/ai-hub/powerpoint-taskpane/index.html +235 -0
- package/public/ai-hub/powerpoint-taskpane/manifest.xml +30 -0
- package/public/ai-hub/script.js +337 -120
- package/public/ai-hub/styles.css +456 -135
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Sideloads the Word add-in manifest so it appears in Word's Developer Add-ins
|
|
4
|
+
* list without admin rights or AppSource publishing.
|
|
5
|
+
*
|
|
6
|
+
* Windows: writes a registry value under HKCU\SOFTWARE\Microsoft\Office\16.0\WEF\Developer
|
|
7
|
+
* macOS: writes an entry to ~/Library/Containers/com.microsoft.Word/Data/Documents/wef/
|
|
8
|
+
*
|
|
9
|
+
* Both paths are non-admin and survive app updates (keyed by manifest GUID).
|
|
10
|
+
* Safe to call multiple times — checks before writing.
|
|
11
|
+
*/
|
|
12
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
13
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
14
|
+
};
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.isSideloaded = isSideloaded;
|
|
17
|
+
exports.sideloadManifest = sideloadManifest;
|
|
18
|
+
exports.removeSideload = removeSideload;
|
|
19
|
+
const fs_1 = __importDefault(require("fs"));
|
|
20
|
+
const path_1 = __importDefault(require("path"));
|
|
21
|
+
const os_1 = __importDefault(require("os"));
|
|
22
|
+
const child_process_1 = require("child_process");
|
|
23
|
+
const MANIFEST_GUID = 'd1090951-50cf-4cf2-9d12-b0f8541d265c';
|
|
24
|
+
function resolveManifestPath(projectPath) {
|
|
25
|
+
const candidates = [
|
|
26
|
+
path_1.default.resolve(projectPath, 'extensions/office-word/manifest.xml'),
|
|
27
|
+
path_1.default.resolve(__dirname, '..', '..', 'extensions/office-word/manifest.xml'),
|
|
28
|
+
path_1.default.resolve(__dirname, '..', '..', '..', 'extensions/office-word/manifest.xml'),
|
|
29
|
+
];
|
|
30
|
+
return candidates.find(c => fs_1.default.existsSync(c)) ?? null;
|
|
31
|
+
}
|
|
32
|
+
function isSideloaded() {
|
|
33
|
+
if (process.platform === 'win32') {
|
|
34
|
+
const result = (0, child_process_1.spawnSync)('reg', [
|
|
35
|
+
'query',
|
|
36
|
+
`HKCU\\SOFTWARE\\Microsoft\\Office\\16.0\\WEF\\Developer`,
|
|
37
|
+
'/v', MANIFEST_GUID,
|
|
38
|
+
], { encoding: 'utf8' });
|
|
39
|
+
return result.status === 0 && result.stdout.includes(MANIFEST_GUID);
|
|
40
|
+
}
|
|
41
|
+
if (process.platform === 'darwin') {
|
|
42
|
+
const wefDir = path_1.default.join(os_1.default.homedir(), 'Library', 'Containers', 'com.microsoft.Word', 'Data', 'Documents', 'wef');
|
|
43
|
+
return fs_1.default.existsSync(path_1.default.join(wefDir, `${MANIFEST_GUID}.xml`));
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
function sideloadManifest(projectPath) {
|
|
48
|
+
const manifestPath = resolveManifestPath(projectPath);
|
|
49
|
+
if (!manifestPath) {
|
|
50
|
+
return { ok: false, reason: 'Manifest file not found — is extensions/office-word/ present?' };
|
|
51
|
+
}
|
|
52
|
+
if (process.platform === 'win32') {
|
|
53
|
+
const result = (0, child_process_1.spawnSync)('reg', [
|
|
54
|
+
'add',
|
|
55
|
+
`HKCU\\SOFTWARE\\Microsoft\\Office\\16.0\\WEF\\Developer`,
|
|
56
|
+
'/v', MANIFEST_GUID,
|
|
57
|
+
'/t', 'REG_SZ',
|
|
58
|
+
'/d', manifestPath,
|
|
59
|
+
'/f',
|
|
60
|
+
], { encoding: 'utf8' });
|
|
61
|
+
if (result.status !== 0) {
|
|
62
|
+
return { ok: false, reason: result.stderr || 'reg add failed' };
|
|
63
|
+
}
|
|
64
|
+
return { ok: true };
|
|
65
|
+
}
|
|
66
|
+
if (process.platform === 'darwin') {
|
|
67
|
+
const wefDir = path_1.default.join(os_1.default.homedir(), 'Library', 'Containers', 'com.microsoft.Word', 'Data', 'Documents', 'wef');
|
|
68
|
+
try {
|
|
69
|
+
fs_1.default.mkdirSync(wefDir, { recursive: true });
|
|
70
|
+
fs_1.default.copyFileSync(manifestPath, path_1.default.join(wefDir, `${MANIFEST_GUID}.xml`));
|
|
71
|
+
return { ok: true };
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
return { ok: false, reason: String(err) };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return { ok: false, reason: `Unsupported platform: ${process.platform}` };
|
|
78
|
+
}
|
|
79
|
+
function removeSideload() {
|
|
80
|
+
if (process.platform === 'win32') {
|
|
81
|
+
(0, child_process_1.spawnSync)('reg', [
|
|
82
|
+
'delete',
|
|
83
|
+
`HKCU\\SOFTWARE\\Microsoft\\Office\\16.0\\WEF\\Developer`,
|
|
84
|
+
'/v', MANIFEST_GUID,
|
|
85
|
+
'/f',
|
|
86
|
+
], { encoding: 'utf8' });
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (process.platform === 'darwin') {
|
|
90
|
+
const wefDir = path_1.default.join(os_1.default.homedir(), 'Library', 'Containers', 'com.microsoft.Word', 'Data', 'Documents', 'wef');
|
|
91
|
+
const target = path_1.default.join(wefDir, `${MANIFEST_GUID}.xml`);
|
|
92
|
+
if (fs_1.default.existsSync(target))
|
|
93
|
+
fs_1.default.unlinkSync(target);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -218,12 +218,18 @@ const configureIDEMCP = async (ide, fraimKey, tokens, providerConfigs) => {
|
|
|
218
218
|
// Merge MCP servers intelligently
|
|
219
219
|
const mergedMCPServers = { ...existingMCPServers };
|
|
220
220
|
const addedServers = [];
|
|
221
|
+
const updatedServers = [];
|
|
221
222
|
const skippedServers = [];
|
|
223
|
+
const alwaysUpdateServers = new Set(['fraim', 'github', 'gitlab', 'jira', 'ado', 'linear']);
|
|
222
224
|
for (const [serverName, serverConfig] of Object.entries(newMCPServers)) {
|
|
223
225
|
if (!existingMCPServers[serverName]) {
|
|
224
226
|
mergedMCPServers[serverName] = serverConfig;
|
|
225
227
|
addedServers.push(serverName);
|
|
226
228
|
}
|
|
229
|
+
else if (alwaysUpdateServers.has(serverName)) {
|
|
230
|
+
mergedMCPServers[serverName] = serverConfig;
|
|
231
|
+
updatedServers.push(serverName);
|
|
232
|
+
}
|
|
227
233
|
else {
|
|
228
234
|
skippedServers.push(serverName);
|
|
229
235
|
}
|
|
@@ -239,6 +245,9 @@ const configureIDEMCP = async (ide, fraimKey, tokens, providerConfigs) => {
|
|
|
239
245
|
addedServers.forEach(server => {
|
|
240
246
|
console.log(chalk_1.default.green(` ✅ Added ${server} MCP server`));
|
|
241
247
|
});
|
|
248
|
+
updatedServers.forEach(server => {
|
|
249
|
+
console.log(chalk_1.default.blue(` Updated ${server} MCP server`));
|
|
250
|
+
});
|
|
242
251
|
skippedServers.forEach(server => {
|
|
243
252
|
console.log(chalk_1.default.gray(` ⏭️ Skipped ${server} (already exists)`));
|
|
244
253
|
});
|
|
@@ -11,7 +11,6 @@ const provider_client_1 = require("../api/provider-client");
|
|
|
11
11
|
const script_sync_utils_1 = require("../utils/script-sync-utils");
|
|
12
12
|
const fs_1 = __importDefault(require("fs"));
|
|
13
13
|
const path_1 = __importDefault(require("path"));
|
|
14
|
-
const version_utils_1 = require("../utils/version-utils");
|
|
15
14
|
const auto_mcp_setup_1 = require("../setup/auto-mcp-setup");
|
|
16
15
|
exports.loginCommand = new commander_1.Command('login')
|
|
17
16
|
.description('Login to platforms (GitHub, etc.)');
|
|
@@ -51,7 +50,7 @@ exports.loginCommand
|
|
|
51
50
|
...(config.tokens || {}),
|
|
52
51
|
github: token
|
|
53
52
|
};
|
|
54
|
-
|
|
53
|
+
delete config.version;
|
|
55
54
|
config.updatedAt = new Date().toISOString();
|
|
56
55
|
fs_1.default.writeFileSync(globalConfigPath, JSON.stringify(config, null, 2));
|
|
57
56
|
console.log(chalk_1.default.green('\n✅ GitHub token saved to global configuration.'));
|
|
@@ -44,7 +44,6 @@ const prompts_1 = __importDefault(require("prompts"));
|
|
|
44
44
|
const fs_1 = __importDefault(require("fs"));
|
|
45
45
|
const path_1 = __importDefault(require("path"));
|
|
46
46
|
const auto_mcp_setup_1 = require("../setup/auto-mcp-setup");
|
|
47
|
-
const version_utils_1 = require("../utils/version-utils");
|
|
48
47
|
const provider_client_1 = require("../api/provider-client");
|
|
49
48
|
const provider_prompts_1 = require("../setup/provider-prompts");
|
|
50
49
|
const provider_registry_1 = require("../providers/provider-registry");
|
|
@@ -225,7 +224,6 @@ const saveGlobalConfig = (fraimKey, mode, tokens, configs) => {
|
|
|
225
224
|
};
|
|
226
225
|
});
|
|
227
226
|
const config = {
|
|
228
|
-
version: (0, version_utils_1.getFraimVersion)(),
|
|
229
227
|
apiKey: fraimKey,
|
|
230
228
|
mode: mode,
|
|
231
229
|
tokens: {
|
|
@@ -84,22 +84,30 @@ function loadUserApiKey() {
|
|
|
84
84
|
return undefined;
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
|
-
function
|
|
87
|
+
function writeSyncMetadata(fraimDir, mode, remoteUrl) {
|
|
88
|
+
fs_1.default.mkdirSync(fraimDir, { recursive: true });
|
|
89
|
+
fs_1.default.writeFileSync(path_1.default.join(fraimDir, '.sync-metadata.json'), JSON.stringify({
|
|
90
|
+
localVersion: (0, version_utils_1.getFraimVersion)(),
|
|
91
|
+
mode,
|
|
92
|
+
remoteUrl,
|
|
93
|
+
syncedAt: new Date().toISOString()
|
|
94
|
+
}, null, 2), 'utf8');
|
|
95
|
+
}
|
|
96
|
+
function removeLegacyVersionFromConfig(fraimDir) {
|
|
88
97
|
const configPath = path_1.default.join(fraimDir, 'config.json');
|
|
89
98
|
if (!fs_1.default.existsSync(configPath)) {
|
|
90
99
|
return;
|
|
91
100
|
}
|
|
92
101
|
try {
|
|
93
102
|
const currentConfig = JSON.parse(fs_1.default.readFileSync(configPath, 'utf8'));
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
currentConfig.version = newVersion;
|
|
103
|
+
if (Object.prototype.hasOwnProperty.call(currentConfig, 'version')) {
|
|
104
|
+
delete currentConfig.version;
|
|
97
105
|
fs_1.default.writeFileSync(configPath, JSON.stringify(currentConfig, null, 2));
|
|
98
|
-
console.log(chalk_1.default.green(
|
|
106
|
+
console.log(chalk_1.default.green('Removed legacy config.version from config.json.'));
|
|
99
107
|
}
|
|
100
108
|
}
|
|
101
109
|
catch {
|
|
102
|
-
console.warn(chalk_1.default.yellow('Could not
|
|
110
|
+
console.warn(chalk_1.default.yellow('Could not remove legacy version from config.json.'));
|
|
103
111
|
}
|
|
104
112
|
}
|
|
105
113
|
function failSync(mode, message) {
|
|
@@ -141,7 +149,7 @@ const runSync = async (options) => {
|
|
|
141
149
|
if (isGlobal && !options.skipUpdates) {
|
|
142
150
|
console.log(chalk_1.default.yellow('You are running a global installation of FRAIM.'));
|
|
143
151
|
console.log(chalk_1.default.gray('Updates are not automatic in this mode.'));
|
|
144
|
-
console.log(chalk_1.default.cyan('Recommended: Use "npx fraim
|
|
152
|
+
console.log(chalk_1.default.cyan('Recommended: Use "npx fraim@latest sync" instead.\n'));
|
|
145
153
|
}
|
|
146
154
|
const { syncFromRemote } = await Promise.resolve().then(() => __importStar(require('../utils/remote-sync')));
|
|
147
155
|
// Allow `FRAIM_LOCAL_SYNC=1` to flip into local-mode without needing
|
|
@@ -163,7 +171,8 @@ const runSync = async (options) => {
|
|
|
163
171
|
});
|
|
164
172
|
if (result.success) {
|
|
165
173
|
console.log(chalk_1.default.green(`Successfully synced ${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`));
|
|
166
|
-
|
|
174
|
+
removeLegacyVersionFromConfig(fraimDir);
|
|
175
|
+
writeSyncMetadata(fraimDir, 'local', localUrl);
|
|
167
176
|
refreshLocalIgnoreConfig();
|
|
168
177
|
const adapterUpdates = (0, agent_adapters_1.ensureAgentAdapterFiles)(projectRoot);
|
|
169
178
|
if (adapterUpdates.length > 0) {
|
|
@@ -210,13 +219,13 @@ const runSync = async (options) => {
|
|
|
210
219
|
console.error(chalk_1.default.yellow('Check your API key and network connection.'));
|
|
211
220
|
if (process.env.TEST_MODE === 'true') {
|
|
212
221
|
console.log(chalk_1.default.yellow('TEST_MODE: Continuing without remote sync (server may be unavailable).'));
|
|
213
|
-
updateVersionInConfig(fraimDir);
|
|
214
222
|
return;
|
|
215
223
|
}
|
|
216
224
|
failSync(failHard, `Remote sync failed: ${result.error}`);
|
|
217
225
|
}
|
|
218
226
|
console.log(chalk_1.default.green(`Successfully synced ${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`));
|
|
219
|
-
|
|
227
|
+
removeLegacyVersionFromConfig(fraimDir);
|
|
228
|
+
writeSyncMetadata(fraimDir, 'remote', config.remoteUrl || process.env.FRAIM_REMOTE_URL || 'https://fraim.wellnessatwork.me');
|
|
220
229
|
refreshLocalIgnoreConfig();
|
|
221
230
|
if (config.repository?.provider === 'github' && (0, github_workflow_sync_1.isGitHubWorkflowAutomationEnabled)(config)) {
|
|
222
231
|
const workflowBundle = await (0, github_workflow_sync_1.fetchGitHubWorkflowBundle)({
|
|
@@ -41,13 +41,66 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
41
41
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
42
42
|
};
|
|
43
43
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
44
|
+
exports.getNpmMajorVersion = getNpmMajorVersion;
|
|
45
|
+
exports.diagnoseFraimMcpLaunchPlan = diagnoseFraimMcpLaunchPlan;
|
|
44
46
|
exports.getMCPConnectivityChecks = getMCPConnectivityChecks;
|
|
45
47
|
const fs_1 = __importDefault(require("fs"));
|
|
46
48
|
const path_1 = __importDefault(require("path"));
|
|
47
49
|
const os_1 = __importDefault(require("os"));
|
|
50
|
+
const child_process_1 = require("child_process");
|
|
48
51
|
const axios_1 = __importDefault(require("axios"));
|
|
49
52
|
const toml = __importStar(require("toml"));
|
|
50
53
|
const ide_detector_1 = require("../../setup/ide-detector");
|
|
54
|
+
const fraim_mcp_latest_launcher_1 = require("../../mcp/fraim-mcp-latest-launcher");
|
|
55
|
+
function getNpmMajorVersion() {
|
|
56
|
+
try {
|
|
57
|
+
const command = process.platform === 'win32' ? (process.env.ComSpec || 'cmd.exe') : 'npm';
|
|
58
|
+
const args = process.platform === 'win32' ? ['/d', '/s', '/c', 'npm --version'] : ['--version'];
|
|
59
|
+
const output = (0, child_process_1.execFileSync)(command, args, {
|
|
60
|
+
encoding: 'utf8',
|
|
61
|
+
stdio: ['ignore', 'pipe', 'ignore']
|
|
62
|
+
});
|
|
63
|
+
const major = Number.parseInt(output.trim().split('.')[0], 10);
|
|
64
|
+
return Number.isFinite(major) ? major : null;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function diagnoseFraimMcpLaunchPlan(fraimServer, platform = process.platform, npmMajorVersion = getNpmMajorVersion()) {
|
|
71
|
+
const command = String(fraimServer?.command || '');
|
|
72
|
+
const args = Array.isArray(fraimServer?.args) ? fraimServer.args.map(String) : [];
|
|
73
|
+
const usesDirectLatest = command.toLowerCase().includes('npx')
|
|
74
|
+
&& args.some((arg) => arg === 'fraim-framework@latest' || arg === 'fraim@latest');
|
|
75
|
+
if (usesDirectLatest) {
|
|
76
|
+
const isKnownBrokenWindowsNpm = platform === 'win32' && npmMajorVersion !== null && npmMajorVersion >= 11;
|
|
77
|
+
return {
|
|
78
|
+
status: isKnownBrokenWindowsNpm ? 'error' : 'warning',
|
|
79
|
+
message: isKnownBrokenWindowsNpm
|
|
80
|
+
? 'FRAIM MCP config uses direct npx @latest, which is fragile on Windows npm 11'
|
|
81
|
+
: 'FRAIM MCP config uses direct npx @latest',
|
|
82
|
+
suggestion: 'Run: fraim add-ide to regenerate MCP config with the FRAIM latest launcher',
|
|
83
|
+
details: {
|
|
84
|
+
launchPlanSource: 'direct-npx-latest',
|
|
85
|
+
platform,
|
|
86
|
+
npmMajorVersion,
|
|
87
|
+
args
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
const launcherPath = (0, fraim_mcp_latest_launcher_1.getFraimMcpLatestLauncherPath)();
|
|
92
|
+
const usesLatestLauncher = command === process.execPath && args.length > 0 && path_1.default.resolve(args[0]) === path_1.default.resolve(launcherPath);
|
|
93
|
+
if (usesLatestLauncher) {
|
|
94
|
+
return {
|
|
95
|
+
status: 'passed',
|
|
96
|
+
message: 'FRAIM MCP config uses the latest launcher',
|
|
97
|
+
details: {
|
|
98
|
+
launchPlanSource: 'fraim-latest-launcher'
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
51
104
|
/**
|
|
52
105
|
* Test FRAIM connectivity by calling fraim_connect
|
|
53
106
|
*/
|
|
@@ -325,6 +378,16 @@ async function validateIDEMCPConfig(ide) {
|
|
|
325
378
|
details: { configPath }
|
|
326
379
|
};
|
|
327
380
|
}
|
|
381
|
+
const launchPlanDiagnostic = diagnoseFraimMcpLaunchPlan(fraimServer);
|
|
382
|
+
if (launchPlanDiagnostic && launchPlanDiagnostic.status !== 'passed') {
|
|
383
|
+
return {
|
|
384
|
+
...launchPlanDiagnostic,
|
|
385
|
+
details: {
|
|
386
|
+
...launchPlanDiagnostic.details,
|
|
387
|
+
configPath
|
|
388
|
+
}
|
|
389
|
+
};
|
|
390
|
+
}
|
|
328
391
|
// Check for auth header (for HTTP servers)
|
|
329
392
|
if (fraimServer.url) {
|
|
330
393
|
const hasAuth = fraimServer.headers?.Authorization || fraimServer.http_headers?.Authorization;
|
|
@@ -339,10 +402,11 @@ async function validateIDEMCPConfig(ide) {
|
|
|
339
402
|
}
|
|
340
403
|
return {
|
|
341
404
|
status: 'passed',
|
|
342
|
-
message: `${ide.name} MCP config valid`,
|
|
405
|
+
message: launchPlanDiagnostic?.message || `${ide.name} MCP config valid`,
|
|
343
406
|
details: {
|
|
344
407
|
configPath,
|
|
345
|
-
serverCount: Object.keys(servers).length
|
|
408
|
+
serverCount: Object.keys(servers).length,
|
|
409
|
+
...(launchPlanDiagnostic?.details || {})
|
|
346
410
|
}
|
|
347
411
|
};
|
|
348
412
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
3
|
* Job checks for FRAIM doctor command
|
|
4
|
-
* Validates job installation
|
|
4
|
+
* Validates job installation
|
|
5
5
|
* Issue #144: Enhanced doctor command
|
|
6
6
|
*/
|
|
7
7
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
@@ -100,69 +100,6 @@ function checkJobStubsPresent() {
|
|
|
100
100
|
}
|
|
101
101
|
};
|
|
102
102
|
}
|
|
103
|
-
/**
|
|
104
|
-
* Check if jobs are up to date
|
|
105
|
-
*/
|
|
106
|
-
function checkJobsUpToDate() {
|
|
107
|
-
return {
|
|
108
|
-
name: 'Jobs up to date',
|
|
109
|
-
category: 'jobs',
|
|
110
|
-
critical: false,
|
|
111
|
-
run: async () => {
|
|
112
|
-
const configPath = (0, project_fraim_paths_1.getWorkspaceConfigPath)(process.cwd());
|
|
113
|
-
if (!fs_1.default.existsSync(configPath)) {
|
|
114
|
-
return {
|
|
115
|
-
status: 'warning',
|
|
116
|
-
message: 'Cannot check job version - config missing'
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
try {
|
|
120
|
-
const config = JSON.parse(fs_1.default.readFileSync(configPath, 'utf8'));
|
|
121
|
-
const possiblePaths = [
|
|
122
|
-
path_1.default.join(__dirname, '../../../../package.json'),
|
|
123
|
-
path_1.default.join(__dirname, '../../../package.json'),
|
|
124
|
-
path_1.default.join(process.cwd(), 'package.json')
|
|
125
|
-
];
|
|
126
|
-
let packageJson = null;
|
|
127
|
-
for (const pkgPath of possiblePaths) {
|
|
128
|
-
if (fs_1.default.existsSync(pkgPath)) {
|
|
129
|
-
packageJson = JSON.parse(fs_1.default.readFileSync(pkgPath, 'utf8'));
|
|
130
|
-
break;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
if (!packageJson) {
|
|
134
|
-
return {
|
|
135
|
-
status: 'warning',
|
|
136
|
-
message: 'Cannot determine package version'
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
const currentVersion = packageJson.version;
|
|
140
|
-
const configVersion = config.version;
|
|
141
|
-
if (configVersion === currentVersion) {
|
|
142
|
-
return {
|
|
143
|
-
status: 'passed',
|
|
144
|
-
message: `Jobs up to date (v${currentVersion})`,
|
|
145
|
-
details: { version: currentVersion }
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
return {
|
|
149
|
-
status: 'warning',
|
|
150
|
-
message: `Jobs outdated (v${configVersion} -> v${currentVersion})`,
|
|
151
|
-
suggestion: 'Update local job stubs with fraim sync.',
|
|
152
|
-
command: 'fraim sync',
|
|
153
|
-
details: { configVersion, currentVersion }
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
catch (error) {
|
|
157
|
-
return {
|
|
158
|
-
status: 'error',
|
|
159
|
-
message: 'Failed to check job version',
|
|
160
|
-
details: { error: error.message }
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
103
|
/**
|
|
167
104
|
* Check if override syntax is valid
|
|
168
105
|
*/
|
|
@@ -245,7 +182,6 @@ function getJobChecks() {
|
|
|
245
182
|
return [
|
|
246
183
|
checkJobsDirectoryExists(),
|
|
247
184
|
checkJobStubsPresent(),
|
|
248
|
-
checkJobsUpToDate(),
|
|
249
185
|
checkOverrideSyntaxValid()
|
|
250
186
|
];
|
|
251
187
|
}
|
|
@@ -0,0 +1,136 @@
|
|
|
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.getFraimMcpLatestLauncherPath = getFraimMcpLatestLauncherPath;
|
|
7
|
+
exports.ensureFraimMcpLatestLauncher = ensureFraimMcpLatestLauncher;
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const os_1 = __importDefault(require("os"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const LAUNCHER_VERSION = 1;
|
|
12
|
+
const launcherSource = `#!/usr/bin/env node
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const os = require('os');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const { spawnSync } = require('child_process');
|
|
17
|
+
|
|
18
|
+
const LAUNCHER_VERSION = ${LAUNCHER_VERSION};
|
|
19
|
+
const USER_DIR = process.env.FRAIM_USER_DIR || path.join(os.homedir(), '.fraim');
|
|
20
|
+
const STATE_DIR = path.join(USER_DIR, 'mcp-launcher');
|
|
21
|
+
const STATE_PATH = path.join(STATE_DIR, 'latest.json');
|
|
22
|
+
const CACHE_ROOT = path.join(USER_DIR, 'npm-cache', 'mcp');
|
|
23
|
+
|
|
24
|
+
const commandName = (name) => process.platform === 'win32' ? name + '.cmd' : name;
|
|
25
|
+
const npmCommand = commandName('npm');
|
|
26
|
+
const npxCommand = commandName('npx');
|
|
27
|
+
const cliArgs = process.argv.slice(2).length > 0 ? process.argv.slice(2) : ['mcp'];
|
|
28
|
+
|
|
29
|
+
function quoteCmdArg(arg) {
|
|
30
|
+
const value = String(arg);
|
|
31
|
+
return /[\\s"&|<>^]/.test(value) ? '"' + value.replace(/"/g, '""') + '"' : value;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function runCommand(command, args, options) {
|
|
35
|
+
if (process.platform !== 'win32') {
|
|
36
|
+
return spawnSync(command, args, options);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return spawnSync(process.env.ComSpec || 'cmd.exe', ['/d', '/s', '/c', [command, ...args.map(quoteCmdArg)].join(' ')], options);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function ensureDir(dir) {
|
|
43
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function readState() {
|
|
47
|
+
try {
|
|
48
|
+
return JSON.parse(fs.readFileSync(STATE_PATH, 'utf8'));
|
|
49
|
+
} catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function writeState(state) {
|
|
55
|
+
ensureDir(STATE_DIR);
|
|
56
|
+
fs.writeFileSync(STATE_PATH, JSON.stringify({ ...state, launcherVersion: LAUNCHER_VERSION, updatedAt: new Date().toISOString() }, null, 2));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function resolveLatest(packageName) {
|
|
60
|
+
const result = runCommand(npmCommand, ['view', packageName, 'version', '--silent'], {
|
|
61
|
+
encoding: 'utf8',
|
|
62
|
+
env: process.env
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
if (result.status !== 0) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const version = String(result.stdout || '').trim();
|
|
70
|
+
return /^\\d+\\.\\d+\\.\\d+/.test(version) ? version : null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function resolvePackage() {
|
|
74
|
+
const fraimVersion = resolveLatest('fraim');
|
|
75
|
+
if (fraimVersion) {
|
|
76
|
+
const state = { packageName: 'fraim', binName: 'fraim', version: fraimVersion, fromCache: false };
|
|
77
|
+
writeState(state);
|
|
78
|
+
return state;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const cached = readState();
|
|
82
|
+
if (cached && cached.packageName === 'fraim' && cached.binName === 'fraim' && cached.version) {
|
|
83
|
+
console.error('[fraim-mcp-launcher] Could not resolve npm latest; using cached fraim@' + cached.version + '.');
|
|
84
|
+
return { packageName: 'fraim', binName: 'fraim', version: cached.version, fromCache: true };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
console.error('[fraim-mcp-launcher] Could not resolve fraim latest from npm and no cached fraim version exists.');
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function runPackage(plan, cacheDir) {
|
|
92
|
+
ensureDir(cacheDir);
|
|
93
|
+
const env = { ...process.env, npm_config_cache: cacheDir };
|
|
94
|
+
const result = runCommand(npxCommand, ['--yes', '--package', plan.packageName + '@' + plan.version, plan.binName, ...cliArgs], {
|
|
95
|
+
stdio: 'inherit',
|
|
96
|
+
env
|
|
97
|
+
});
|
|
98
|
+
return typeof result.status === 'number' ? result.status : 1;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const plan = resolvePackage();
|
|
102
|
+
const versionCacheDir = path.join(CACHE_ROOT, plan.packageName + '-' + plan.version.replace(/[^a-zA-Z0-9._-]/g, '_'));
|
|
103
|
+
let status = runPackage(plan, versionCacheDir);
|
|
104
|
+
|
|
105
|
+
if (status !== 0 && process.platform === 'win32') {
|
|
106
|
+
const retryCache = path.join(CACHE_ROOT, 'retry-' + Date.now() + '-' + process.pid);
|
|
107
|
+
console.error('[fraim-mcp-launcher] Package execution failed; retrying once with a fresh npm cache.');
|
|
108
|
+
status = runPackage(plan, retryCache);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
process.exit(status);
|
|
112
|
+
`;
|
|
113
|
+
function getFraimMcpLatestLauncherPath() {
|
|
114
|
+
return path_1.default.join(process.env.FRAIM_USER_DIR || path_1.default.join(os_1.default.homedir(), '.fraim'), 'bin', 'fraim-mcp-latest.js');
|
|
115
|
+
}
|
|
116
|
+
function ensureFraimMcpLatestLauncher() {
|
|
117
|
+
const launcherPath = getFraimMcpLatestLauncherPath();
|
|
118
|
+
const launcherDir = path_1.default.dirname(launcherPath);
|
|
119
|
+
fs_1.default.mkdirSync(launcherDir, { recursive: true });
|
|
120
|
+
if (!fs_1.default.existsSync(launcherPath) || fs_1.default.readFileSync(launcherPath, 'utf8') !== launcherSource) {
|
|
121
|
+
fs_1.default.writeFileSync(launcherPath, launcherSource, 'utf8');
|
|
122
|
+
if (process.platform !== 'win32') {
|
|
123
|
+
try {
|
|
124
|
+
fs_1.default.chmodSync(launcherPath, 0o755);
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
// Best effort only. IDE configs invoke this through node, so chmod is not required.
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
command: process.execPath,
|
|
133
|
+
args: [launcherPath],
|
|
134
|
+
path: launcherPath
|
|
135
|
+
};
|
|
136
|
+
}
|
|
@@ -10,6 +10,7 @@ exports.isHTTPServer = isHTTPServer;
|
|
|
10
10
|
exports.buildAllBaseServers = buildAllBaseServers;
|
|
11
11
|
const provider_registry_1 = require("../providers/provider-registry");
|
|
12
12
|
const command_resolution_1 = require("./command-resolution");
|
|
13
|
+
const fraim_mcp_latest_launcher_1 = require("./fraim-mcp-latest-launcher");
|
|
13
14
|
exports.BASE_MCP_SERVERS = [
|
|
14
15
|
{
|
|
15
16
|
id: 'git',
|
|
@@ -33,16 +34,19 @@ exports.BASE_MCP_SERVERS = [
|
|
|
33
34
|
id: 'fraim',
|
|
34
35
|
name: 'FRAIM',
|
|
35
36
|
description: 'FRAIM job orchestration and mentoring',
|
|
36
|
-
buildServer: (fraimKey) =>
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
37
|
+
buildServer: (fraimKey) => {
|
|
38
|
+
const launcher = (0, fraim_mcp_latest_launcher_1.ensureFraimMcpLatestLauncher)();
|
|
39
|
+
return {
|
|
40
|
+
command: launcher.command,
|
|
41
|
+
args: launcher.args,
|
|
42
|
+
env: {
|
|
43
|
+
// Include API key for IDE configs (Codex, VSCode, etc.)
|
|
44
|
+
// The stdio-server will use this if set, otherwise falls back to ~/.fraim/config.json
|
|
45
|
+
FRAIM_API_KEY: fraimKey,
|
|
46
|
+
FRAIM_REMOTE_URL: 'https://fraim.wellnessatwork.me'
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
46
50
|
}
|
|
47
51
|
];
|
|
48
52
|
// ============================================================================
|
|
@@ -338,7 +338,7 @@ const autoConfigureMCP = async (fraimKey, tokenInput, selectedIDEs, providerConf
|
|
|
338
338
|
console.log(chalk_1.default.blue('\n🎯 Next steps:'));
|
|
339
339
|
console.log(chalk_1.default.cyan(' 1. Restart your configured IDEs'));
|
|
340
340
|
console.log(chalk_1.default.cyan(' 2. Go to any project directory'));
|
|
341
|
-
console.log(chalk_1.default.cyan(' 3. Run: npx fraim
|
|
341
|
+
console.log(chalk_1.default.cyan(' 3. Run: npx fraim@latest init-project'));
|
|
342
342
|
console.log(chalk_1.default.cyan(' 4. Tell your AI agent: "Onboard this project"'));
|
|
343
343
|
}
|
|
344
344
|
};
|
|
@@ -12,6 +12,7 @@ exports.FRAIM_SYNC_GITIGNORE_ENTRIES = [
|
|
|
12
12
|
'fraim/ai-employee/',
|
|
13
13
|
'fraim/ai-manager/',
|
|
14
14
|
'fraim/docs/',
|
|
15
|
+
'fraim/.sync-metadata.json',
|
|
15
16
|
];
|
|
16
17
|
const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
17
18
|
const managedBlockPattern = new RegExp(`\\n?${escapeRegExp(exports.FRAIM_SYNC_GITIGNORE_START)}[\\s\\S]*?${escapeRegExp(exports.FRAIM_SYNC_GITIGNORE_END)}\\n?`, 'm');
|
|
@@ -43,6 +44,16 @@ function resolveGitInfoExcludePath(projectRoot) {
|
|
|
43
44
|
const gitDir = path_1.default.isAbsolute(match[1])
|
|
44
45
|
? match[1]
|
|
45
46
|
: path_1.default.resolve(projectRoot, match[1]);
|
|
47
|
+
const commonDirPath = path_1.default.join(gitDir, 'commondir');
|
|
48
|
+
if (fs_1.default.existsSync(commonDirPath)) {
|
|
49
|
+
const commonDirRaw = fs_1.default.readFileSync(commonDirPath, 'utf8').trim();
|
|
50
|
+
if (commonDirRaw.length > 0) {
|
|
51
|
+
const commonDir = path_1.default.isAbsolute(commonDirRaw)
|
|
52
|
+
? commonDirRaw
|
|
53
|
+
: path_1.default.resolve(gitDir, commonDirRaw);
|
|
54
|
+
return path_1.default.join(commonDir, 'info', 'exclude');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
46
57
|
return path_1.default.join(gitDir, 'info', 'exclude');
|
|
47
58
|
}
|
|
48
59
|
const removeFraimSyncedContentGitignoreBlock = (projectRoot) => {
|
|
@@ -31,8 +31,8 @@ function shouldLockSyncedContent() {
|
|
|
31
31
|
}
|
|
32
32
|
function getSyncedContentLockTargets(projectRoot) {
|
|
33
33
|
return fraim_gitignore_1.FRAIM_SYNC_GITIGNORE_ENTRIES
|
|
34
|
+
.filter((entry) => /[\\/]$/.test(entry))
|
|
34
35
|
.map((entry) => entry.replace(/[\\/]+$/, ''))
|
|
35
|
-
.filter((entry) => entry.length > 0)
|
|
36
36
|
.map((entry) => (0, path_1.join)(projectRoot, entry));
|
|
37
37
|
}
|
|
38
38
|
function setFileWriteLockRecursively(dirPath, readOnly) {
|
|
@@ -88,7 +88,6 @@ function normalizeAutomation(config) {
|
|
|
88
88
|
function normalizeFraimConfig(config) {
|
|
89
89
|
// Handle backward compatibility and migration
|
|
90
90
|
const mergedConfig = {
|
|
91
|
-
version: config.version || types_1.DEFAULT_FRAIM_CONFIG.version,
|
|
92
91
|
project: {
|
|
93
92
|
...types_1.DEFAULT_FRAIM_CONFIG.project,
|
|
94
93
|
...(config.project || {})
|
|
@@ -154,7 +153,7 @@ function loadFraimConfig(configPath = (0, project_fraim_paths_1.getWorkspaceConf
|
|
|
154
153
|
const configContent = (0, fs_1.readFileSync)(configPath, 'utf-8');
|
|
155
154
|
const config = JSON.parse(configContent);
|
|
156
155
|
const mergedConfig = normalizeFraimConfig(config);
|
|
157
|
-
console.log(`Loaded FRAIM config from ${displayPath}
|
|
156
|
+
console.log(`Loaded FRAIM config from ${displayPath}`);
|
|
158
157
|
if (config.git && !config.repository) {
|
|
159
158
|
console.warn('Deprecated: "git" config detected. Consider migrating to "repository" config.');
|
|
160
159
|
}
|