fraim-framework 2.0.151 → 2.0.153
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/hosts.js +36 -11
- package/dist/src/ai-hub/server.js +47 -17
- package/dist/src/cli/commands/test-mcp.js +171 -0
- package/dist/src/cli/setup/first-run.js +242 -0
- package/dist/src/cli/setup/ide-detector.js +46 -18
- package/dist/src/cli/utils/managed-agent-paths.js +48 -0
- package/dist/src/core/config-writer.js +75 -0
- package/dist/src/core/utils/job-aliases.js +47 -0
- package/dist/src/core/utils/workflow-parser.js +174 -0
- package/dist/src/first-run/session-service.js +35 -48
- package/index.js +1 -1
- package/package.json +1 -1
- package/public/ai-hub/index.html +229 -221
- package/public/ai-hub/script.js +472 -408
- package/public/ai-hub/styles.css +660 -582
- package/public/first-run/index.html +35 -35
- package/public/first-run/script.js +667 -667
- package/public/first-run/styles.css +73 -73
package/dist/src/ai-hub/hosts.js
CHANGED
|
@@ -16,6 +16,7 @@ const child_process_1 = require("child_process");
|
|
|
16
16
|
const fs_1 = __importDefault(require("fs"));
|
|
17
17
|
const os_1 = __importDefault(require("os"));
|
|
18
18
|
const path_1 = __importDefault(require("path"));
|
|
19
|
+
const managed_agent_paths_1 = require("../cli/utils/managed-agent-paths");
|
|
19
20
|
// Parse a single line of host stdout looking for a seekMentoring tool-use
|
|
20
21
|
// signal. Returns null if the line does not contain one. Supports both
|
|
21
22
|
// hosts FRAIM ships against today:
|
|
@@ -239,6 +240,7 @@ const availableByVersionProbe = (command) => {
|
|
|
239
240
|
const invocation = resolveHostInvocation({ command, args: ['--version'] });
|
|
240
241
|
const result = (0, child_process_1.spawnSync)(invocation.command, invocation.args, {
|
|
241
242
|
encoding: 'utf8',
|
|
243
|
+
env: { ...process.env, PATH: (0, managed_agent_paths_1.buildPathWithManagedAgentBins)(process.env.PATH) },
|
|
242
244
|
});
|
|
243
245
|
return result.status === 0;
|
|
244
246
|
};
|
|
@@ -269,23 +271,32 @@ function parseFraimInvocation(message) {
|
|
|
269
271
|
// instructions for headless hosts. The Hub still shows the familiar
|
|
270
272
|
// invocation in the manager timeline, but the actual CLI child process
|
|
271
273
|
// receives an explicit instruction that works in non-interactive mode.
|
|
274
|
+
//
|
|
275
|
+
// Important: continue turns must preserve the named FRAIM job. Manager
|
|
276
|
+
// coaching such as `/fraim follow-your-mentor` is not generic advice for
|
|
277
|
+
// the active employee job; it is a new FRAIM workflow that must survive
|
|
278
|
+
// the headless transform.
|
|
272
279
|
function transformHeadlessFraimMessage(message, kind) {
|
|
273
280
|
const parsed = parseFraimInvocation(message);
|
|
274
281
|
if (!parsed)
|
|
275
282
|
return message;
|
|
276
|
-
if (
|
|
277
|
-
if (
|
|
278
|
-
|
|
283
|
+
if (!parsed.jobId) {
|
|
284
|
+
if (kind === 'continue') {
|
|
285
|
+
if (parsed.remainder) {
|
|
286
|
+
return `Continue the active FRAIM job with this manager coaching:\n\n${parsed.remainder}`;
|
|
287
|
+
}
|
|
288
|
+
return 'Continue the active FRAIM job using the current session context.';
|
|
279
289
|
}
|
|
280
|
-
return 'Continue the active FRAIM job using the current session context.';
|
|
281
|
-
}
|
|
282
|
-
if (!parsed.jobId)
|
|
283
290
|
return message;
|
|
291
|
+
}
|
|
284
292
|
const parts = [
|
|
285
293
|
`Call the get_fraim_job MCP tool with job "${parsed.jobId}" to get the full job instructions, then follow them exactly.`,
|
|
286
294
|
];
|
|
295
|
+
if (kind === 'continue') {
|
|
296
|
+
parts.push('\n\nThis is a manager follow-up inside an existing FRAIM session. Continue by running that named FRAIM job, not by freeforming or by resuming a different FRAIM workflow.');
|
|
297
|
+
}
|
|
287
298
|
if (parsed.remainder) {
|
|
288
|
-
parts.push(`\n\
|
|
299
|
+
parts.push(`\n\nManager instructions: ${parsed.remainder}`);
|
|
289
300
|
}
|
|
290
301
|
return parts.join('');
|
|
291
302
|
}
|
|
@@ -487,14 +498,14 @@ class FakeHostRuntime {
|
|
|
487
498
|
return this.employees;
|
|
488
499
|
}
|
|
489
500
|
startRun(hostId, _projectPath, message, handlers) {
|
|
490
|
-
return this.fakeProcess(hostId,
|
|
501
|
+
return this.fakeProcess(hostId, this.fakeEmployeeReply('start', message), handlers);
|
|
491
502
|
}
|
|
492
503
|
continueRun(hostId, _projectPath, sessionId, message, handlers) {
|
|
493
|
-
return this.fakeProcess(hostId,
|
|
504
|
+
return this.fakeProcess(hostId, this.fakeEmployeeReply('continue', message), handlers);
|
|
494
505
|
}
|
|
495
506
|
fakeProcess(_hostId, text, handlers) {
|
|
496
|
-
handlers.onEvent({ sessionId: (0, crypto_1.randomUUID)()
|
|
497
|
-
handlers.onEvent({ message: text
|
|
507
|
+
handlers.onEvent({ sessionId: (0, crypto_1.randomUUID)() }, 'system');
|
|
508
|
+
handlers.onEvent({ message: text }, 'stdout');
|
|
498
509
|
setTimeout(() => handlers.onExit(0), 25);
|
|
499
510
|
return {
|
|
500
511
|
stdout: process.stdout,
|
|
@@ -528,6 +539,20 @@ class FakeHostRuntime {
|
|
|
528
539
|
ref: () => undefined,
|
|
529
540
|
};
|
|
530
541
|
}
|
|
542
|
+
fakeEmployeeReply(kind, message) {
|
|
543
|
+
const parsed = parseFraimInvocation(message);
|
|
544
|
+
const label = parsed?.jobId
|
|
545
|
+
? parsed.jobId.split('-').map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(' ')
|
|
546
|
+
: null;
|
|
547
|
+
if (kind === 'continue') {
|
|
548
|
+
return label
|
|
549
|
+
? `Understood. I'm taking another pass with ${label}.`
|
|
550
|
+
: 'Understood. I am incorporating your coaching now.';
|
|
551
|
+
}
|
|
552
|
+
return label
|
|
553
|
+
? `Understood. I'm starting ${label} now.`
|
|
554
|
+
: 'Understood. I am working on that now.';
|
|
555
|
+
}
|
|
531
556
|
}
|
|
532
557
|
exports.FakeHostRuntime = FakeHostRuntime;
|
|
533
558
|
// Issue #347 — test-only host that lets a test inject seekMentoring
|
|
@@ -41,6 +41,7 @@ const catalog_1 = require("./catalog");
|
|
|
41
41
|
const agent_token_prices_1 = require("../local-mcp-server/agent-token-prices");
|
|
42
42
|
const hosts_1 = require("./hosts");
|
|
43
43
|
const preferences_1 = require("./preferences");
|
|
44
|
+
const managed_agent_paths_1 = require("../cli/utils/managed-agent-paths");
|
|
44
45
|
function loadPersonaCapabilityModule() {
|
|
45
46
|
try {
|
|
46
47
|
// Server deployments include the persona catalog. The npm client package
|
|
@@ -278,9 +279,15 @@ function deriveStages(run, projectPath) {
|
|
|
278
279
|
? declaredPath.findIndex((p) => p.id === run.currentPhase)
|
|
279
280
|
: -1;
|
|
280
281
|
const historyMap = new Map((run.phaseHistory || []).map((e) => [e.phaseId, e]));
|
|
282
|
+
const completedWithoutPhaseTelemetry = run.status === 'completed' &&
|
|
283
|
+
currentIndex < 0 &&
|
|
284
|
+
historyMap.size === 0;
|
|
281
285
|
return declaredPath.map((phase, index) => {
|
|
282
286
|
let state;
|
|
283
|
-
if (
|
|
287
|
+
if (completedWithoutPhaseTelemetry) {
|
|
288
|
+
state = 'done';
|
|
289
|
+
}
|
|
290
|
+
else if (currentIndex < 0) {
|
|
284
291
|
state = 'upcoming';
|
|
285
292
|
}
|
|
286
293
|
else if (index < currentIndex) {
|
|
@@ -313,12 +320,18 @@ function hubAgentOption(hubId) {
|
|
|
313
320
|
const frId = HUB_TO_FIRST_RUN_ID[hubId];
|
|
314
321
|
return frId ? types_1.FIRST_RUN_AGENT_OPTIONS.find((o) => o.id === frId) : undefined;
|
|
315
322
|
}
|
|
316
|
-
function hubCommandVersion(command) {
|
|
323
|
+
function hubCommandVersion(command, extraBinDirs) {
|
|
317
324
|
const executable = process.platform === 'win32' ? 'cmd.exe' : command;
|
|
318
325
|
const args = process.platform === 'win32'
|
|
319
326
|
? ['/d', '/s', '/c', `${command} --version`]
|
|
320
327
|
: ['--version'];
|
|
321
|
-
const
|
|
328
|
+
const env = extraBinDirs && extraBinDirs.length > 0
|
|
329
|
+
? {
|
|
330
|
+
...process.env,
|
|
331
|
+
PATH: [...new Set([...extraBinDirs, ...(process.env.PATH || '').split(path_1.default.delimiter).filter(Boolean)])].join(path_1.default.delimiter),
|
|
332
|
+
}
|
|
333
|
+
: undefined;
|
|
334
|
+
const result = (0, child_process_1.spawnSync)(executable, args, { encoding: 'utf8', timeout: 5000, ...(env ? { env } : {}) });
|
|
322
335
|
if (result.status !== 0 || result.error)
|
|
323
336
|
return null;
|
|
324
337
|
const raw = (result.stdout || result.stderr || '').trim();
|
|
@@ -370,6 +383,13 @@ function hubOpenTerminal(command) {
|
|
|
370
383
|
}
|
|
371
384
|
(0, child_process_1.spawn)('bash', ['-c', command], { detached: true, stdio: 'ignore' }).unref();
|
|
372
385
|
}
|
|
386
|
+
function buildManagedLoginCommand(command) {
|
|
387
|
+
const managedPath = (0, managed_agent_paths_1.buildPathWithManagedAgentBins)(process.env.PATH);
|
|
388
|
+
if (process.platform === 'win32') {
|
|
389
|
+
return `set "PATH=${managedPath}" && ${command}`;
|
|
390
|
+
}
|
|
391
|
+
return `export PATH="${managedPath}"; ${command}`;
|
|
392
|
+
}
|
|
373
393
|
function getUserHubDir() {
|
|
374
394
|
return path_1.default.join(os_1.default.homedir(), '.fraim');
|
|
375
395
|
}
|
|
@@ -394,9 +414,11 @@ class AiHubServer {
|
|
|
394
414
|
this.hostRuntime = options.hostRuntime || (process.env.FRAIM_AI_HUB_FAKE_HOST === '1' ? new hosts_1.FakeHostRuntime() : new hosts_1.CliHostRuntime());
|
|
395
415
|
if (options.dbService !== undefined) {
|
|
396
416
|
this.dbService = options.dbService;
|
|
417
|
+
this.ownsDbService = false;
|
|
397
418
|
}
|
|
398
419
|
else {
|
|
399
420
|
this.dbService = createDefaultDbService();
|
|
421
|
+
this.ownsDbService = this.dbService !== undefined;
|
|
400
422
|
}
|
|
401
423
|
this.app.use(express_1.default.json());
|
|
402
424
|
this.app.use('/ai-hub', express_1.default.static(resolveAiHubPublicDir()));
|
|
@@ -425,18 +447,22 @@ class AiHubServer {
|
|
|
425
447
|
});
|
|
426
448
|
}
|
|
427
449
|
async stop() {
|
|
428
|
-
if (
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
450
|
+
if (this.httpServer) {
|
|
451
|
+
await new Promise((resolve, reject) => {
|
|
452
|
+
this.httpServer.close((error) => {
|
|
453
|
+
if (error) {
|
|
454
|
+
reject(error);
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
resolve();
|
|
458
|
+
});
|
|
437
459
|
});
|
|
438
|
-
|
|
439
|
-
|
|
460
|
+
this.httpServer = undefined;
|
|
461
|
+
}
|
|
462
|
+
if (this.ownsDbService && this.dbService) {
|
|
463
|
+
await this.dbService.close();
|
|
464
|
+
this.dbService = undefined;
|
|
465
|
+
}
|
|
440
466
|
}
|
|
441
467
|
async bootstrapResponse(projectPath, apiKey) {
|
|
442
468
|
const normalizedProjectPath = path_1.default.resolve(projectPath || this.projectPath);
|
|
@@ -557,9 +583,13 @@ class AiHubServer {
|
|
|
557
583
|
if (!option)
|
|
558
584
|
return res.status(400).json({ error: `Unknown agent: ${hubId}` });
|
|
559
585
|
try {
|
|
560
|
-
const prefix =
|
|
586
|
+
const prefix = (0, managed_agent_paths_1.getManagedNodeRoot)();
|
|
561
587
|
fs_1.default.mkdirSync(prefix, { recursive: true });
|
|
562
588
|
await hubRunProcess('npm', ['install', '-g', option.installPackage], { npm_config_prefix: prefix });
|
|
589
|
+
const ver = hubCommandVersion(option.launchCommand, (0, managed_agent_paths_1.getManagedAgentBinDirs)());
|
|
590
|
+
if (!ver) {
|
|
591
|
+
throw new Error(`${option.label} install completed, but the CLI is not runnable from FRAIM's managed PATH.`);
|
|
592
|
+
}
|
|
563
593
|
return res.json({
|
|
564
594
|
ok: true,
|
|
565
595
|
message: `${option.label} installed successfully.`,
|
|
@@ -581,7 +611,7 @@ class AiHubServer {
|
|
|
581
611
|
if (!option)
|
|
582
612
|
return res.status(400).json({ error: `Unknown agent: ${hubId}` });
|
|
583
613
|
try {
|
|
584
|
-
hubOpenTerminal(option.loginCommand);
|
|
614
|
+
hubOpenTerminal(buildManagedLoginCommand(option.loginCommand));
|
|
585
615
|
return res.json({
|
|
586
616
|
ok: true,
|
|
587
617
|
message: `A terminal window opened with the ${option.label} sign-in command. Complete sign-in there, then return here.`,
|
|
@@ -602,7 +632,7 @@ class AiHubServer {
|
|
|
602
632
|
const option = hubAgentOption(hubId);
|
|
603
633
|
if (!option)
|
|
604
634
|
return res.status(400).json({ error: `Unknown agent: ${hubId}` });
|
|
605
|
-
const ver = hubCommandVersion(option.launchCommand);
|
|
635
|
+
const ver = hubCommandVersion(option.launchCommand, (0, managed_agent_paths_1.getManagedAgentBinDirs)());
|
|
606
636
|
if (ver) {
|
|
607
637
|
return res.json({ ok: true, ready: true, message: `${option.label} is ready.` });
|
|
608
638
|
}
|
|
@@ -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;
|