fraim-framework 2.0.168 → 2.0.170
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/dist/src/ai-hub/server.js +51 -4
- package/dist/src/ai-hub/word-sideload.js +95 -0
- package/dist/src/cli/commands/test-mcp.js +171 -0
- package/dist/src/cli/setup/first-run.js +242 -0
- package/dist/src/core/config-writer.js +75 -0
- package/dist/src/core/utils/inheritance-parser.js +5 -0
- package/dist/src/core/utils/job-aliases.js +47 -0
- package/dist/src/core/utils/workflow-parser.js +174 -0
- package/index.js +1 -1
- package/package.json +1 -1
- package/public/ai-hub/powerpoint-taskpane/index.html +236 -236
- package/public/ai-hub/powerpoint-taskpane/manifest.xml +29 -29
- package/public/ai-hub/script.js +10 -8
- package/public/portfolio/ashley.html +1 -1
- package/public/portfolio/casey.html +1 -1
- package/public/portfolio/celia.html +1 -1
- package/public/portfolio/gautam.html +1 -1
- package/public/portfolio/hari.html +1 -1
- package/public/portfolio/maestro.html +1 -1
- package/public/portfolio/mandy.html +1 -1
- package/public/portfolio/pam.html +6 -6
- package/public/portfolio/qasm.html +1 -1
- package/public/portfolio/sade.html +1 -1
- package/public/portfolio/sam.html +1 -1
- package/public/portfolio/swen.html +6 -6
|
@@ -50,7 +50,6 @@ const https_1 = __importDefault(require("https"));
|
|
|
50
50
|
const types_1 = require("../first-run/types");
|
|
51
51
|
const learning_context_builder_1 = require("../local-mcp-server/learning-context-builder");
|
|
52
52
|
const project_fraim_paths_1 = require("../core/utils/project-fraim-paths");
|
|
53
|
-
const persona_hiring_1 = require("../config/persona-hiring");
|
|
54
53
|
const catalog_1 = require("./catalog");
|
|
55
54
|
const agent_token_prices_1 = require("../local-mcp-server/agent-token-prices");
|
|
56
55
|
const hosts_1 = require("./hosts");
|
|
@@ -59,6 +58,44 @@ const preferences_1 = require("./preferences");
|
|
|
59
58
|
const conversation_store_1 = require("./conversation-store");
|
|
60
59
|
const managed_browser_1 = require("./managed-browser");
|
|
61
60
|
const managed_agent_paths_1 = require("../cli/utils/managed-agent-paths");
|
|
61
|
+
let personaHiringModule;
|
|
62
|
+
let managerHiringModule;
|
|
63
|
+
function loadPersonaHiringModule() {
|
|
64
|
+
const cached = personaHiringModule;
|
|
65
|
+
if (cached !== undefined) {
|
|
66
|
+
return cached;
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
// Server deployments include the persona catalog. The npm client package
|
|
70
|
+
// intentionally does not, so importing ai-hub/server must not require it.
|
|
71
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
72
|
+
const loaded = require('../config/persona-hiring');
|
|
73
|
+
personaHiringModule = loaded;
|
|
74
|
+
return loaded;
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
personaHiringModule = null;
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function loadManagerHiringModule() {
|
|
82
|
+
const cached = managerHiringModule;
|
|
83
|
+
if (cached !== undefined) {
|
|
84
|
+
return cached;
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
// Server deployments include the manager-hiring catalog. The npm client
|
|
88
|
+
// package intentionally does not, so Hub bootstrap must not require it.
|
|
89
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
90
|
+
const loaded = require('../config/ai-manager-hiring');
|
|
91
|
+
managerHiringModule = loaded;
|
|
92
|
+
return loaded;
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
managerHiringModule = null;
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
62
99
|
function loadPersonaCapabilityModule() {
|
|
63
100
|
try {
|
|
64
101
|
// Server deployments include the persona catalog. The npm client package
|
|
@@ -80,6 +117,16 @@ function buildHubPersonaHireUrl(personaKey, hireMode = 'job') {
|
|
|
80
117
|
const params = new URLSearchParams({ persona: personaKey, mode: hireMode });
|
|
81
118
|
return `/pricing?${params.toString()}`;
|
|
82
119
|
}
|
|
120
|
+
function buildHubPersonaAvatarUrl(personaKey) {
|
|
121
|
+
return loadPersonaHiringModule()?.buildPersonaAvatarUrl(personaKey) ?? '';
|
|
122
|
+
}
|
|
123
|
+
function buildHubManagerHiringCatalog() {
|
|
124
|
+
return loadManagerHiringModule()?.buildManagerHiringCatalog() ?? {
|
|
125
|
+
qualities: [],
|
|
126
|
+
services: [],
|
|
127
|
+
roles: {},
|
|
128
|
+
};
|
|
129
|
+
}
|
|
83
130
|
async function getHubWorkspacePersonaState(dbService, userId, apiKey) {
|
|
84
131
|
try {
|
|
85
132
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
@@ -1016,7 +1063,7 @@ class AiHubServer {
|
|
|
1016
1063
|
// Issue #538 — source of truth for the "Hire a human manager" UI. Lazy-required
|
|
1017
1064
|
// (not a top-level import) so the lightweight client CLI paths that import this
|
|
1018
1065
|
// module for findAvailablePort do not eagerly load server-only config (repro #422).
|
|
1019
|
-
managerHiring:
|
|
1066
|
+
managerHiring: buildHubManagerHiringCatalog(),
|
|
1020
1067
|
// Issue #540: server-authoritative manager team (personas assigned by this manager).
|
|
1021
1068
|
managerTeam,
|
|
1022
1069
|
};
|
|
@@ -1175,7 +1222,7 @@ class AiHubServer {
|
|
|
1175
1222
|
key: bundle.personaKey,
|
|
1176
1223
|
displayName: bundle.catalogMetadata.displayName,
|
|
1177
1224
|
role: bundle.catalogMetadata.role,
|
|
1178
|
-
avatarUrl: (
|
|
1225
|
+
avatarUrl: buildHubPersonaAvatarUrl(bundle.personaKey),
|
|
1179
1226
|
pricingLabel: bundle.catalogMetadata.pricingLabel,
|
|
1180
1227
|
status: 'locked',
|
|
1181
1228
|
hireUrl: buildHubPersonaHireUrl(bundle.personaKey, bundle.defaultHireMode),
|
|
@@ -1213,7 +1260,7 @@ class AiHubServer {
|
|
|
1213
1260
|
key: bundle.personaKey,
|
|
1214
1261
|
displayName: bundle.catalogMetadata.displayName,
|
|
1215
1262
|
role: bundle.catalogMetadata.role,
|
|
1216
|
-
avatarUrl: (
|
|
1263
|
+
avatarUrl: buildHubPersonaAvatarUrl(bundle.personaKey),
|
|
1217
1264
|
pricingLabel: hiredKeys.has(bundle.personaKey) ? '' : bundle.catalogMetadata.pricingLabel,
|
|
1218
1265
|
status: (hiredKeys.has(bundle.personaKey) ? 'hired' : 'locked'),
|
|
1219
1266
|
hireUrl: buildHubPersonaHireUrl(bundle.personaKey, bundle.defaultHireMode),
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.testMCPCommand = exports.runTestMCP = void 0;
|
|
40
|
+
const commander_1 = require("commander");
|
|
41
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
42
|
+
const fs_1 = __importDefault(require("fs"));
|
|
43
|
+
const path_1 = __importDefault(require("path"));
|
|
44
|
+
const ide_detector_1 = require("../setup/ide-detector");
|
|
45
|
+
const script_sync_utils_1 = require("../utils/script-sync-utils");
|
|
46
|
+
const testIDEConfig = async (ide) => {
|
|
47
|
+
const result = {
|
|
48
|
+
ide: ide.name,
|
|
49
|
+
configExists: false,
|
|
50
|
+
configValid: false,
|
|
51
|
+
mcpServers: [],
|
|
52
|
+
errors: []
|
|
53
|
+
};
|
|
54
|
+
const configPath = (0, ide_detector_1.expandPath)(ide.configPath);
|
|
55
|
+
if (!fs_1.default.existsSync(configPath)) {
|
|
56
|
+
result.errors.push('Config file does not exist');
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
result.configExists = true;
|
|
60
|
+
try {
|
|
61
|
+
if (ide.configFormat === 'json') {
|
|
62
|
+
const configContent = fs_1.default.readFileSync(configPath, 'utf8');
|
|
63
|
+
const config = JSON.parse(configContent);
|
|
64
|
+
const servers = ide.configType === 'vscode' ? config.servers : config.mcpServers;
|
|
65
|
+
if (servers) {
|
|
66
|
+
result.configValid = true;
|
|
67
|
+
result.mcpServers = Object.keys(servers);
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
const expectedKey = ide.configType === 'vscode' ? 'servers' : 'mcpServers';
|
|
71
|
+
result.errors.push(`No ${expectedKey} section found`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
else if (ide.configFormat === 'toml') {
|
|
75
|
+
const configContent = fs_1.default.readFileSync(configPath, 'utf8');
|
|
76
|
+
// Simple TOML parsing for MCP servers
|
|
77
|
+
const serverMatches = configContent.match(/\[mcp_servers\.(\w+)\]/g);
|
|
78
|
+
if (serverMatches) {
|
|
79
|
+
result.configValid = true;
|
|
80
|
+
result.mcpServers = serverMatches.map(match => match.replace(/\[mcp_servers\.(\w+)\]/, '$1'));
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
result.errors.push('No mcp_servers sections found');
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
result.errors.push(`Failed to parse config: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
89
|
+
}
|
|
90
|
+
return result;
|
|
91
|
+
};
|
|
92
|
+
const checkGlobalSetup = () => {
|
|
93
|
+
const globalConfigPath = path_1.default.join((0, script_sync_utils_1.getUserFraimDir)(), 'config.json');
|
|
94
|
+
return fs_1.default.existsSync(globalConfigPath);
|
|
95
|
+
};
|
|
96
|
+
const runTestMCP = async () => {
|
|
97
|
+
console.log(chalk_1.default.blue('🔍 Testing MCP configuration...\n'));
|
|
98
|
+
// Check global setup
|
|
99
|
+
if (!checkGlobalSetup()) {
|
|
100
|
+
console.log(chalk_1.default.red('❌ Global FRAIM setup not found.'));
|
|
101
|
+
console.log(chalk_1.default.yellow('Please run: fraim setup --key=<your-fraim-key>'));
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
console.log(chalk_1.default.green('✅ Global FRAIM setup found'));
|
|
105
|
+
// Detect IDEs
|
|
106
|
+
const detectedIDEs = (0, ide_detector_1.detectInstalledIDEs)();
|
|
107
|
+
if (detectedIDEs.length === 0) {
|
|
108
|
+
console.log(chalk_1.default.yellow('⚠️ No supported IDEs detected.'));
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
console.log(chalk_1.default.blue(`\n🔍 Testing ${detectedIDEs.length} detected IDEs...\n`));
|
|
112
|
+
const results = await Promise.all(detectedIDEs.map(ide => testIDEConfig(ide)));
|
|
113
|
+
let totalConfigured = 0;
|
|
114
|
+
let totalWithFRAIM = 0;
|
|
115
|
+
for (const result of results) {
|
|
116
|
+
console.log(chalk_1.default.white(`📱 ${result.ide}`));
|
|
117
|
+
if (!result.configExists) {
|
|
118
|
+
console.log(chalk_1.default.red(' ❌ No MCP config found'));
|
|
119
|
+
console.log(chalk_1.default.gray(` 💡 Run: fraim setup --ide=${result.ide.toLowerCase()}`));
|
|
120
|
+
}
|
|
121
|
+
else if (!result.configValid) {
|
|
122
|
+
console.log(chalk_1.default.yellow(' ⚠️ Config exists but invalid'));
|
|
123
|
+
result.errors.forEach(error => {
|
|
124
|
+
console.log(chalk_1.default.red(` ❌ ${error}`));
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
totalConfigured++;
|
|
129
|
+
console.log(chalk_1.default.green(` ✅ MCP config valid (${result.mcpServers.length} servers)`));
|
|
130
|
+
// Check for essential servers
|
|
131
|
+
const { BASE_MCP_SERVERS } = await Promise.resolve().then(() => __importStar(require('../mcp/mcp-server-registry')));
|
|
132
|
+
const essentialServers = BASE_MCP_SERVERS.map(s => s.id); // fraim, git, playwright
|
|
133
|
+
const hasEssential = essentialServers.filter(server => result.mcpServers.includes(server));
|
|
134
|
+
if (hasEssential.includes('fraim')) {
|
|
135
|
+
totalWithFRAIM++;
|
|
136
|
+
console.log(chalk_1.default.green(' ✅ FRAIM server configured'));
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
console.log(chalk_1.default.yellow(' ⚠️ FRAIM server missing'));
|
|
140
|
+
}
|
|
141
|
+
if (hasEssential.length > 1) {
|
|
142
|
+
console.log(chalk_1.default.green(` ✅ ${hasEssential.length - 1} additional servers: ${hasEssential.filter(s => s !== 'fraim').join(', ')}`));
|
|
143
|
+
}
|
|
144
|
+
const missingEssential = essentialServers.filter(server => !result.mcpServers.includes(server));
|
|
145
|
+
if (missingEssential.length > 0) {
|
|
146
|
+
console.log(chalk_1.default.yellow(` ⚠️ Missing servers: ${missingEssential.join(', ')}`));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
console.log(); // Empty line
|
|
150
|
+
}
|
|
151
|
+
// Summary
|
|
152
|
+
console.log(chalk_1.default.blue('📊 Summary:'));
|
|
153
|
+
console.log(chalk_1.default.green(` ✅ ${totalConfigured}/${detectedIDEs.length} IDEs have valid MCP configs`));
|
|
154
|
+
console.log(chalk_1.default.green(` ✅ ${totalWithFRAIM}/${detectedIDEs.length} IDEs have FRAIM configured`));
|
|
155
|
+
if (totalWithFRAIM === 0) {
|
|
156
|
+
console.log(chalk_1.default.red('\n❌ No IDEs have FRAIM configured!'));
|
|
157
|
+
console.log(chalk_1.default.yellow('💡 Run: fraim setup --key=<your-fraim-key>'));
|
|
158
|
+
}
|
|
159
|
+
else if (totalWithFRAIM < detectedIDEs.length) {
|
|
160
|
+
console.log(chalk_1.default.yellow(`\n⚠️ ${detectedIDEs.length - totalWithFRAIM} IDEs missing FRAIM configuration`));
|
|
161
|
+
console.log(chalk_1.default.yellow('💡 Run: fraim setup to configure remaining IDEs'));
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
console.log(chalk_1.default.green('\n🎉 All detected IDEs have FRAIM configured!'));
|
|
165
|
+
console.log(chalk_1.default.blue('💡 Try running: fraim init-project in any project'));
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
exports.runTestMCP = runTestMCP;
|
|
169
|
+
exports.testMCPCommand = new commander_1.Command('test-mcp')
|
|
170
|
+
.description('Test MCP server configurations for all detected IDEs')
|
|
171
|
+
.action(exports.runTestMCP);
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.runFirstRunExperience = void 0;
|
|
40
|
+
const prompts_1 = __importDefault(require("prompts"));
|
|
41
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
42
|
+
const fs_1 = __importDefault(require("fs"));
|
|
43
|
+
const path_1 = __importDefault(require("path"));
|
|
44
|
+
const os_1 = __importDefault(require("os"));
|
|
45
|
+
const script_sync_utils_1 = require("../utils/script-sync-utils");
|
|
46
|
+
const project_fraim_paths_1 = require("../../core/utils/project-fraim-paths");
|
|
47
|
+
const loadGlobalConfig = () => {
|
|
48
|
+
const globalConfigPath = path_1.default.join((0, script_sync_utils_1.getUserFraimDir)(), 'config.json');
|
|
49
|
+
if (!fs_1.default.existsSync(globalConfigPath)) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
const config = JSON.parse(fs_1.default.readFileSync(globalConfigPath, 'utf8'));
|
|
54
|
+
return {
|
|
55
|
+
fraimKey: config.apiKey,
|
|
56
|
+
githubToken: config.githubToken
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
catch (e) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
const checkMCPConfigurations = () => {
|
|
64
|
+
const sources = [];
|
|
65
|
+
// Check Kiro MCP config
|
|
66
|
+
const kiroConfigPath = path_1.default.join(os_1.default.homedir(), '.kiro', 'settings', 'mcp.json');
|
|
67
|
+
if (fs_1.default.existsSync(kiroConfigPath)) {
|
|
68
|
+
try {
|
|
69
|
+
const kiroConfig = JSON.parse(fs_1.default.readFileSync(kiroConfigPath, 'utf8'));
|
|
70
|
+
if (kiroConfig.mcpServers?.fraim) {
|
|
71
|
+
sources.push('Kiro');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch (e) {
|
|
75
|
+
// Ignore parsing errors
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// Check Claude Desktop / Cowork config
|
|
79
|
+
const claudeConfigPath = path_1.default.join(os_1.default.homedir(), 'AppData', 'Roaming', 'Claude', 'claude_desktop_config.json');
|
|
80
|
+
if (fs_1.default.existsSync(claudeConfigPath)) {
|
|
81
|
+
try {
|
|
82
|
+
const claudeConfig = JSON.parse(fs_1.default.readFileSync(claudeConfigPath, 'utf8'));
|
|
83
|
+
if (claudeConfig.mcpServers?.fraim) {
|
|
84
|
+
sources.push('Claude Desktop / Cowork');
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch (e) {
|
|
88
|
+
// Ignore parsing errors
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// Check macOS Claude Desktop / Cowork config path
|
|
92
|
+
const claudeMacPath = path_1.default.join(os_1.default.homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
|
|
93
|
+
if (fs_1.default.existsSync(claudeMacPath)) {
|
|
94
|
+
try {
|
|
95
|
+
const claudeConfig = JSON.parse(fs_1.default.readFileSync(claudeMacPath, 'utf8'));
|
|
96
|
+
if (claudeConfig.mcpServers?.fraim) {
|
|
97
|
+
sources.push('Claude Desktop / Cowork (macOS)');
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
catch (e) {
|
|
101
|
+
// Ignore parsing errors
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// Check VS Code MCP config (uses "servers" key)
|
|
105
|
+
const vscodePaths = [
|
|
106
|
+
path_1.default.join(os_1.default.homedir(), 'AppData', 'Roaming', 'Code', 'User', 'mcp.json'),
|
|
107
|
+
path_1.default.join(os_1.default.homedir(), 'Library', 'Application Support', 'Code', 'User', 'mcp.json'),
|
|
108
|
+
path_1.default.join(os_1.default.homedir(), '.config', 'Code', 'User', 'mcp.json')
|
|
109
|
+
];
|
|
110
|
+
for (const vscodePath of vscodePaths) {
|
|
111
|
+
if (fs_1.default.existsSync(vscodePath)) {
|
|
112
|
+
try {
|
|
113
|
+
const vscodeConfig = JSON.parse(fs_1.default.readFileSync(vscodePath, 'utf8'));
|
|
114
|
+
if (vscodeConfig.servers?.fraim) {
|
|
115
|
+
sources.push('VSCode');
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
catch (e) {
|
|
120
|
+
// Ignore parsing errors
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
found: sources.length > 0,
|
|
126
|
+
sources
|
|
127
|
+
};
|
|
128
|
+
};
|
|
129
|
+
const runFirstRunExperience = async () => {
|
|
130
|
+
// Skip interactive setup in CI/Test environments
|
|
131
|
+
if (process.env.CI === 'true' || process.env.TEST_MODE === 'true') {
|
|
132
|
+
console.log(chalk_1.default.yellow('ℹ️ Skipping interactive first-run experience (CI/TEST_MODE detected)'));
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
console.log(chalk_1.default.blue('\n👋 Welcome to FRAIM! Let\'s get you set up.\n'));
|
|
136
|
+
// Check for existing configuration
|
|
137
|
+
const globalConfig = loadGlobalConfig();
|
|
138
|
+
const mcpCheck = checkMCPConfigurations();
|
|
139
|
+
if (globalConfig && globalConfig.fraimKey) {
|
|
140
|
+
console.log(chalk_1.default.green('✅ Found existing FRAIM configuration'));
|
|
141
|
+
if (mcpCheck.found) {
|
|
142
|
+
console.log(chalk_1.default.green(`✅ Found FRAIM MCP configuration in: ${mcpCheck.sources.join(', ')}`));
|
|
143
|
+
console.log(chalk_1.default.blue('🎉 You\'re all set! FRAIM is ready to use.\n'));
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
console.log(chalk_1.default.yellow('⚠️ FRAIM key found but no MCP configuration detected.'));
|
|
147
|
+
console.log(chalk_1.default.cyan('💡 Consider running "fraim add-ide" to configure your IDE.\n'));
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
else if (mcpCheck.found) {
|
|
151
|
+
console.log(chalk_1.default.green(`✅ Found FRAIM MCP configuration in: ${mcpCheck.sources.join(', ')}`));
|
|
152
|
+
console.log(chalk_1.default.yellow('⚠️ But no global FRAIM configuration found.'));
|
|
153
|
+
console.log(chalk_1.default.cyan('💡 Your MCP setup looks good! Skipping key setup.\n'));
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
// No existing configuration found - ask for FRAIM key
|
|
157
|
+
console.log(chalk_1.default.yellow('🔍 No existing FRAIM configuration detected.\n'));
|
|
158
|
+
let response;
|
|
159
|
+
try {
|
|
160
|
+
response = await (0, prompts_1.default)({
|
|
161
|
+
type: 'text',
|
|
162
|
+
name: 'fraimKey',
|
|
163
|
+
message: 'Do you have a FRAIM key? (Input key or press Enter to skip)',
|
|
164
|
+
validate: (value) => true // Optional input
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
catch (e) {
|
|
168
|
+
console.warn(chalk_1.default.yellow('\n⚠️ Interactive prompts experienced an issue. Skipping setup.\n'));
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (response.fraimKey && response.fraimKey.trim().length > 0) {
|
|
172
|
+
const key = response.fraimKey.trim();
|
|
173
|
+
console.log(chalk_1.default.green('\n✅ Key received.'));
|
|
174
|
+
console.log(chalk_1.default.yellow('\nPlease add the following to your IDE\'s MCP config (e.g. claude_desktop_config.json):'));
|
|
175
|
+
const mcpConfig = {
|
|
176
|
+
mcpServers: {
|
|
177
|
+
fraim: {
|
|
178
|
+
serverUrl: "https://fraim.wellnessatwork.me",
|
|
179
|
+
headers: {
|
|
180
|
+
"x-api-key": key
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
console.log(chalk_1.default.cyan(JSON.stringify(mcpConfig, null, 2)));
|
|
186
|
+
console.log('\n');
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
console.log(chalk_1.default.yellow('\nℹ️ If you need a key, please email sid.mathur@gmail.com to request one.\n'));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
// 2. Ask about Architecture Document
|
|
193
|
+
const archResponse = await (0, prompts_1.default)({
|
|
194
|
+
type: 'confirm',
|
|
195
|
+
name: 'hasArchDoc',
|
|
196
|
+
message: 'Do you have an architecture document for this project?',
|
|
197
|
+
initial: false
|
|
198
|
+
});
|
|
199
|
+
if (!archResponse.hasArchDoc) {
|
|
200
|
+
console.log(chalk_1.default.yellow('\n💡 To create an architecture document, please ask your IDE Agent:'));
|
|
201
|
+
console.log(chalk_1.default.cyan(' "Run the bootstrap/create-architecture workflow"'));
|
|
202
|
+
console.log(chalk_1.default.gray(' (This ensures your agent understands your codebase structure)\n'));
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
const pathResponse = await (0, prompts_1.default)({
|
|
206
|
+
type: 'text',
|
|
207
|
+
name: 'archDocPath',
|
|
208
|
+
message: 'Please provide the relative path to your architecture document:',
|
|
209
|
+
initial: 'docs/architecture.md',
|
|
210
|
+
validate: (value) => value.length > 0 ? true : 'Path cannot be empty'
|
|
211
|
+
});
|
|
212
|
+
if (pathResponse.archDocPath) {
|
|
213
|
+
const fs = await Promise.resolve().then(() => __importStar(require('fs')));
|
|
214
|
+
const path = await Promise.resolve().then(() => __importStar(require('path')));
|
|
215
|
+
const resolvedPath = path.resolve(process.cwd(), pathResponse.archDocPath);
|
|
216
|
+
if (fs.existsSync(resolvedPath)) {
|
|
217
|
+
try {
|
|
218
|
+
const configPath = (0, project_fraim_paths_1.getWorkspaceConfigPath)(process.cwd());
|
|
219
|
+
if (fs.existsSync(configPath)) {
|
|
220
|
+
const configContent = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
221
|
+
if (!configContent.customizations)
|
|
222
|
+
configContent.customizations = {};
|
|
223
|
+
configContent.customizations.architectureDoc = pathResponse.archDocPath;
|
|
224
|
+
fs.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
|
|
225
|
+
console.log(chalk_1.default.green(`\n✅ Linked architecture document: ${pathResponse.archDocPath}`));
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
console.log(chalk_1.default.red(`\n❌ ${(0, project_fraim_paths_1.getWorkspaceFraimDisplayPath)('config.json')} not found. Could not save preference.`));
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
catch (e) {
|
|
232
|
+
console.error(chalk_1.default.red('\n❌ Failed to update config:'), e);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
console.log(chalk_1.default.yellow(`\n⚠️ File not found at ${pathResponse.archDocPath}. We'll skip linking it for now.`));
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
console.log(chalk_1.default.green(`\n✅ Jobs are installed in ${(0, project_fraim_paths_1.getWorkspaceFraimDisplayPath)('ai-employee/jobs')} and ${(0, project_fraim_paths_1.getWorkspaceFraimDisplayPath)('ai-manager/jobs')}.\n`));
|
|
241
|
+
};
|
|
242
|
+
exports.runFirstRunExperience = runFirstRunExperience;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.readFraimConfigRaw = readFraimConfigRaw;
|
|
4
|
+
exports.mergeFraimConfig = mergeFraimConfig;
|
|
5
|
+
exports.writeFraimConfigUpdate = writeFraimConfigUpdate;
|
|
6
|
+
exports.writeFraimConfig = writeFraimConfig;
|
|
7
|
+
const fs_1 = require("fs");
|
|
8
|
+
const path_1 = require("path");
|
|
9
|
+
const config_loader_1 = require("./config-loader");
|
|
10
|
+
const types_1 = require("./types");
|
|
11
|
+
function isPlainObject(value) {
|
|
12
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
13
|
+
}
|
|
14
|
+
function deepMerge(baseValue, updateValue) {
|
|
15
|
+
if (updateValue === undefined) {
|
|
16
|
+
return baseValue;
|
|
17
|
+
}
|
|
18
|
+
if (Array.isArray(updateValue)) {
|
|
19
|
+
return [...updateValue];
|
|
20
|
+
}
|
|
21
|
+
if (!isPlainObject(updateValue)) {
|
|
22
|
+
return updateValue;
|
|
23
|
+
}
|
|
24
|
+
const baseObject = isPlainObject(baseValue) ? baseValue : {};
|
|
25
|
+
const merged = { ...baseObject };
|
|
26
|
+
for (const [key, value] of Object.entries(updateValue)) {
|
|
27
|
+
merged[key] = deepMerge(baseObject[key], value);
|
|
28
|
+
}
|
|
29
|
+
return merged;
|
|
30
|
+
}
|
|
31
|
+
function ensureWritableFraimConfigShape(rawConfig) {
|
|
32
|
+
const config = { ...rawConfig };
|
|
33
|
+
if (typeof config.version !== 'string' || config.version.trim().length === 0) {
|
|
34
|
+
config.version = types_1.DEFAULT_FRAIM_CONFIG.version;
|
|
35
|
+
}
|
|
36
|
+
const projectConfig = isPlainObject(config.project) ? config.project : {};
|
|
37
|
+
config.project = {
|
|
38
|
+
...types_1.DEFAULT_FRAIM_CONFIG.project,
|
|
39
|
+
...projectConfig
|
|
40
|
+
};
|
|
41
|
+
const customizationsConfig = isPlainObject(config.customizations) ? config.customizations : {};
|
|
42
|
+
config.customizations = {
|
|
43
|
+
...types_1.DEFAULT_FRAIM_CONFIG.customizations,
|
|
44
|
+
...customizationsConfig
|
|
45
|
+
};
|
|
46
|
+
return config;
|
|
47
|
+
}
|
|
48
|
+
function readFraimConfigRaw(configPath) {
|
|
49
|
+
if (!(0, fs_1.existsSync)(configPath)) {
|
|
50
|
+
return {};
|
|
51
|
+
}
|
|
52
|
+
const parsed = JSON.parse((0, fs_1.readFileSync)(configPath, 'utf8'));
|
|
53
|
+
if (!isPlainObject(parsed)) {
|
|
54
|
+
throw new Error('FRAIM config must contain a JSON object at the top level.');
|
|
55
|
+
}
|
|
56
|
+
return parsed;
|
|
57
|
+
}
|
|
58
|
+
function mergeFraimConfig(existingRawConfig, update) {
|
|
59
|
+
const rawConfig = ensureWritableFraimConfigShape(deepMerge(existingRawConfig, update));
|
|
60
|
+
return {
|
|
61
|
+
config: (0, config_loader_1.normalizeFraimConfig)(rawConfig),
|
|
62
|
+
created: Object.keys(existingRawConfig).length === 0,
|
|
63
|
+
rawConfig
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function writeFraimConfigUpdate(configPath, update) {
|
|
67
|
+
const existingRawConfig = readFraimConfigRaw(configPath);
|
|
68
|
+
const result = mergeFraimConfig(existingRawConfig, update);
|
|
69
|
+
(0, fs_1.mkdirSync)((0, path_1.dirname)(configPath), { recursive: true });
|
|
70
|
+
(0, fs_1.writeFileSync)(configPath, JSON.stringify(result.rawConfig, null, 2));
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
73
|
+
function writeFraimConfig(configPath, config) {
|
|
74
|
+
return writeFraimConfigUpdate(configPath, config);
|
|
75
|
+
}
|