fraim-framework 2.0.63 → 2.0.64
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.
|
@@ -23,6 +23,7 @@ const fs_1 = require("fs");
|
|
|
23
23
|
const path_1 = require("path");
|
|
24
24
|
const os_1 = require("os");
|
|
25
25
|
const child_process_1 = require("child_process");
|
|
26
|
+
const crypto_1 = require("crypto");
|
|
26
27
|
const axios_1 = __importDefault(require("axios"));
|
|
27
28
|
class FraimLocalMCPServer {
|
|
28
29
|
constructor() {
|
|
@@ -34,6 +35,7 @@ class FraimLocalMCPServer {
|
|
|
34
35
|
this.repoInfo = null;
|
|
35
36
|
this.remoteUrl = process.env.FRAIM_REMOTE_URL || 'https://fraim.wellnessatwork.me';
|
|
36
37
|
this.apiKey = process.env.FRAIM_API_KEY || '';
|
|
38
|
+
this.localVersion = this.detectLocalVersion();
|
|
37
39
|
if (!this.apiKey) {
|
|
38
40
|
this.logError('❌ FRAIM_API_KEY environment variable is required');
|
|
39
41
|
process.exit(1);
|
|
@@ -41,13 +43,36 @@ class FraimLocalMCPServer {
|
|
|
41
43
|
this.log('🚀 FRAIM Local MCP Server starting...');
|
|
42
44
|
this.log(`📡 Remote server: ${this.remoteUrl}`);
|
|
43
45
|
this.log(`🔑 API key: ${this.apiKey.substring(0, 10)}...`);
|
|
46
|
+
this.log(`Local MCP version: ${this.localVersion}`);
|
|
44
47
|
}
|
|
45
48
|
log(message) {
|
|
46
49
|
// Log to stderr (stdout is reserved for MCP protocol)
|
|
47
|
-
|
|
50
|
+
const key = this.apiKey || 'MISSING_API_KEY';
|
|
51
|
+
console.error(`[FRAIM key:${key}] ${message}`);
|
|
48
52
|
}
|
|
49
53
|
logError(message) {
|
|
50
|
-
|
|
54
|
+
const key = this.apiKey || 'MISSING_API_KEY';
|
|
55
|
+
console.error(`[FRAIM ERROR key:${key}] ${message}`);
|
|
56
|
+
}
|
|
57
|
+
detectLocalVersion() {
|
|
58
|
+
const candidates = [
|
|
59
|
+
(0, path_1.join)(__dirname, '..', '..', '..', 'package.json'),
|
|
60
|
+
(0, path_1.join)(__dirname, '..', '..', 'package.json')
|
|
61
|
+
];
|
|
62
|
+
for (const pkgPath of candidates) {
|
|
63
|
+
try {
|
|
64
|
+
if (!(0, fs_1.existsSync)(pkgPath))
|
|
65
|
+
continue;
|
|
66
|
+
const pkg = JSON.parse((0, fs_1.readFileSync)(pkgPath, 'utf8'));
|
|
67
|
+
if (typeof pkg.version === 'string' && pkg.version.trim().length > 0) {
|
|
68
|
+
return pkg.version;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
// Ignore and try the next candidate
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return 'unknown';
|
|
51
76
|
}
|
|
52
77
|
findProjectRoot() {
|
|
53
78
|
// If we already have workspace root from MCP roots, use it
|
|
@@ -475,30 +500,31 @@ class FraimLocalMCPServer {
|
|
|
475
500
|
* Proxy request to remote FRAIM server
|
|
476
501
|
*/
|
|
477
502
|
async proxyToRemote(request) {
|
|
503
|
+
const requestId = (0, crypto_1.randomUUID)();
|
|
478
504
|
try {
|
|
479
505
|
// Special handling for fraim_connect - automatically inject machine and repo info
|
|
480
506
|
if (request.method === 'tools/call' && request.params?.name === 'fraim_connect') {
|
|
481
|
-
this.log(
|
|
507
|
+
this.log(`[req:${requestId}] Intercepting fraim_connect to inject machine/repo info`);
|
|
482
508
|
const args = request.params.arguments || {};
|
|
483
509
|
// REQUIRED: Auto-detect and inject machine info
|
|
484
510
|
const detectedMachine = this.detectMachineInfo();
|
|
485
511
|
args.machine = {
|
|
486
|
-
...
|
|
487
|
-
...
|
|
512
|
+
...args.machine, // Agent values as fallback
|
|
513
|
+
...detectedMachine // Detected values override (always win)
|
|
488
514
|
};
|
|
489
|
-
this.log(
|
|
515
|
+
this.log(`[req:${requestId}] Auto-detected and injected machine info: ${args.machine.hostname} (${args.machine.platform}), ${Math.round(args.machine.memory / 1024 / 1024 / 1024)}GB RAM, ${args.machine.cpus} CPUs`);
|
|
490
516
|
// REQUIRED: Auto-detect and inject repo info
|
|
491
517
|
const detectedRepo = this.detectRepoInfo();
|
|
492
518
|
if (detectedRepo) {
|
|
493
519
|
args.repo = {
|
|
494
|
-
...
|
|
495
|
-
...
|
|
520
|
+
...args.repo, // Agent values as fallback
|
|
521
|
+
...detectedRepo // Detected values override (always win)
|
|
496
522
|
};
|
|
497
|
-
this.log(
|
|
523
|
+
this.log(`[req:${requestId}] Auto-detected and injected repo info: ${args.repo.owner}/${args.repo.name}`);
|
|
498
524
|
}
|
|
499
525
|
else {
|
|
500
526
|
// If detection fails completely, return error instead of sending garbage
|
|
501
|
-
this.logError(
|
|
527
|
+
this.logError(`[req:${requestId}] Could not detect repo info and no config available`);
|
|
502
528
|
return {
|
|
503
529
|
jsonrpc: '2.0',
|
|
504
530
|
id: request.id,
|
|
@@ -511,23 +537,53 @@ class FraimLocalMCPServer {
|
|
|
511
537
|
// Update the request with injected info
|
|
512
538
|
request.params.arguments = args;
|
|
513
539
|
}
|
|
540
|
+
this.log(`[req:${requestId}] Proxying ${request.method} to ${this.remoteUrl}/mcp`);
|
|
514
541
|
const response = await axios_1.default.post(`${this.remoteUrl}/mcp`, request, {
|
|
515
542
|
headers: {
|
|
516
543
|
'Content-Type': 'application/json',
|
|
517
|
-
'x-api-key': this.apiKey
|
|
544
|
+
'x-api-key': this.apiKey,
|
|
545
|
+
'x-fraim-request-id': requestId,
|
|
546
|
+
'x-fraim-local-version': this.localVersion
|
|
518
547
|
},
|
|
519
548
|
timeout: 30000
|
|
520
549
|
});
|
|
521
550
|
return response.data;
|
|
522
551
|
}
|
|
523
552
|
catch (error) {
|
|
524
|
-
|
|
553
|
+
const status = error?.response?.status;
|
|
554
|
+
const remoteData = error?.response?.data;
|
|
555
|
+
this.logError(`[req:${requestId}] Remote request failed (${status || 'no-status'}): ${error.message}`);
|
|
556
|
+
if (remoteData && typeof remoteData === 'object') {
|
|
557
|
+
const forwarded = {
|
|
558
|
+
jsonrpc: typeof remoteData.jsonrpc === 'string' ? remoteData.jsonrpc : '2.0',
|
|
559
|
+
id: remoteData.id ?? request.id,
|
|
560
|
+
error: remoteData.error ?? {
|
|
561
|
+
code: -32603,
|
|
562
|
+
message: `Remote server error (${status || 'unknown status'}): ${error.message}`
|
|
563
|
+
}
|
|
564
|
+
};
|
|
565
|
+
if (forwarded.error && typeof forwarded.error === 'object') {
|
|
566
|
+
const existingData = forwarded.error.data;
|
|
567
|
+
forwarded.error.data = {
|
|
568
|
+
...(existingData && typeof existingData === 'object' ? existingData : {}),
|
|
569
|
+
fraimRequestId: requestId,
|
|
570
|
+
remoteStatus: status ?? null,
|
|
571
|
+
localMcpVersion: this.localVersion
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
return forwarded;
|
|
575
|
+
}
|
|
525
576
|
return {
|
|
526
577
|
jsonrpc: '2.0',
|
|
527
578
|
id: request.id,
|
|
528
579
|
error: {
|
|
529
580
|
code: -32603,
|
|
530
|
-
message: `Remote server error: ${error.message}
|
|
581
|
+
message: `Remote server error: ${error.message}`,
|
|
582
|
+
data: {
|
|
583
|
+
fraimRequestId: requestId,
|
|
584
|
+
remoteStatus: status ?? null,
|
|
585
|
+
localMcpVersion: this.localVersion
|
|
586
|
+
}
|
|
531
587
|
}
|
|
532
588
|
};
|
|
533
589
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fraim-framework",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.64",
|
|
4
4
|
"description": "FRAIM v2: Framework for Rigor-based AI Management - Transform from solo developer to AI manager orchestrating production-ready code with enterprise-grade discipline",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"start:fraim": "tsx src/fraim-mcp-server.ts",
|
|
16
16
|
"dev:fraim": "tsx --watch src/fraim-mcp-server.ts",
|
|
17
17
|
"serve:website": "node fraim-pro/serve.js",
|
|
18
|
-
"watch:fraimlogs": "tsx scripts/watch-fraim-logs.ts",
|
|
18
|
+
"watch:fraimlogs": "tsx scripts/watch-fraim-logs.ts > prodlogs.log 2>&1",
|
|
19
19
|
"manage-keys": "tsx scripts/fraim/manage-keys.ts",
|
|
20
20
|
"view-signups": "tsx scripts/view-signups.ts",
|
|
21
21
|
"fraim:init": "npm run build && node index.js init",
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# FRAIM Workflow: cost-optimization
|
|
2
|
+
|
|
3
|
+
> [!IMPORTANT]
|
|
4
|
+
> This is a **FRAIM-managed workflow stub**.
|
|
5
|
+
> To load the full context (rules, templates, and execution steps), ask your AI agent to:
|
|
6
|
+
> `@fraim get_fraim_workflow("cost-optimization")`
|
|
7
|
+
>
|
|
8
|
+
> DO NOT EXECUTE.
|
|
9
|
+
|
|
10
|
+
## Intent
|
|
11
|
+
Enable AI agents to systematically analyze Azure subscription costs, identify optimization opportunities, and implement cost-saving measures while maintaining production reliability.
|