fraim-framework 2.0.93 → 2.0.94

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.
@@ -41,7 +41,6 @@ const commander_1 = require("commander");
41
41
  const fs_1 = __importDefault(require("fs"));
42
42
  const path_1 = __importDefault(require("path"));
43
43
  const chalk_1 = __importDefault(require("chalk"));
44
- const child_process_1 = require("child_process");
45
44
  const config_loader_1 = require("../../core/config-loader");
46
45
  const version_utils_1 = require("../utils/version-utils");
47
46
  const script_sync_utils_1 = require("../utils/script-sync-utils");
@@ -85,14 +84,13 @@ const runSync = async (options) => {
85
84
  const projectRoot = process.cwd();
86
85
  const config = (0, config_loader_1.loadFraimConfig)();
87
86
  const fraimDir = path_1.default.join(projectRoot, '.fraim');
88
- // Check for CLI updates first (but skip during installation to prevent loops)
89
- const isPostInstall = process.env.npm_lifecycle_event === 'postinstall' ||
90
- process.env.npm_lifecycle_script === 'postinstall';
91
- if (!options.skipUpdates && !isPostInstall) {
92
- await checkAndUpdateCLI();
93
- }
94
- else if (isPostInstall) {
95
- console.log(chalk_1.default.gray('⏭️ Skipping auto-update check during installation to prevent loops.'));
87
+ // Check if running via npx
88
+ const isNpx = process.env.npm_config_prefix === undefined || process.env.npm_lifecycle_event === 'npx';
89
+ const isGlobal = !isNpx && (process.env.npm_config_global === 'true' || process.env.npm_config_prefix);
90
+ if (isGlobal && !options.skipUpdates) {
91
+ console.log(chalk_1.default.yellow('⚠️ You are running a global installation of FRAIM.'));
92
+ console.log(chalk_1.default.gray(' Updates are not automatic in this mode.'));
93
+ console.log(chalk_1.default.cyan(' 💡 Recommended: Use "npx fraim-framework@latest sync" instead.\n'));
96
94
  }
97
95
  const { syncFromRemote } = await Promise.resolve().then(() => __importStar(require('../utils/remote-sync')));
98
96
  // Path 1: --local flag → hit localhost MCP server
@@ -149,118 +147,9 @@ const runSync = async (options) => {
149
147
  updateVersionInConfig(fraimDir);
150
148
  };
151
149
  exports.runSync = runSync;
152
- async function checkAndUpdateCLI() {
153
- try {
154
- // Additional safety: check if we're in a global npm install context
155
- const isGlobalInstall = process.env.npm_config_global === 'true' ||
156
- process.env.npm_config_prefix ||
157
- process.cwd().includes('node_modules');
158
- if (isGlobalInstall) {
159
- console.log(chalk_1.default.gray('⏭️ Skipping auto-update during global installation.'));
160
- return;
161
- }
162
- console.log(chalk_1.default.blue('🔍 Checking for FRAIM CLI updates...'));
163
- const currentVersion = (0, version_utils_1.getFraimVersion)();
164
- const latestVersion = await getLatestNpmVersionHttp('fraim-framework');
165
- if (!latestVersion) {
166
- console.log(chalk_1.default.yellow('⚠️ Could not check for updates. Continuing with current version.'));
167
- return;
168
- }
169
- if (isVersionUpToDate(currentVersion, latestVersion)) {
170
- console.log(chalk_1.default.green(`✅ FRAIM CLI is up to date (${currentVersion})`));
171
- return;
172
- }
173
- console.log(chalk_1.default.yellow(`📦 New version available: ${currentVersion} → ${latestVersion}`));
174
- console.log(chalk_1.default.blue('🔄 Auto-updating FRAIM CLI...'));
175
- const success = await updateGlobalPackageHttp('fraim-framework', latestVersion);
176
- if (success) {
177
- console.log(chalk_1.default.green(`✅ Successfully updated to FRAIM ${latestVersion}`));
178
- console.log(chalk_1.default.gray(' Restart may be required for some changes to take effect.'));
179
- }
180
- else {
181
- console.log(chalk_1.default.yellow('⚠️ Auto-update failed. Please run: npm install -g fraim-framework@latest'));
182
- }
183
- }
184
- catch (error) {
185
- console.log(chalk_1.default.yellow('⚠️ Could not check for updates. Continuing with current version.'));
186
- }
187
- }
188
- async function getLatestNpmVersionHttp(packageName) {
189
- try {
190
- // Use Node.js built-in https module instead of spawn
191
- const https = require('https');
192
- return new Promise((resolve) => {
193
- const req = https.get(`https://registry.npmjs.org/${packageName}/latest`, {
194
- timeout: 5000,
195
- headers: {
196
- 'User-Agent': 'fraim-framework-cli'
197
- }
198
- }, (res) => {
199
- let data = '';
200
- res.on('data', (chunk) => {
201
- data += chunk;
202
- });
203
- res.on('end', () => {
204
- try {
205
- const packageInfo = JSON.parse(data);
206
- resolve(packageInfo.version || null);
207
- }
208
- catch (e) {
209
- resolve(null);
210
- }
211
- });
212
- });
213
- req.on('error', () => {
214
- resolve(null);
215
- });
216
- req.on('timeout', () => {
217
- req.destroy();
218
- resolve(null);
219
- });
220
- });
221
- }
222
- catch (error) {
223
- return null;
224
- }
225
- }
226
- function isVersionUpToDate(current, latest) {
227
- // Simple version comparison - assumes semantic versioning
228
- const currentParts = current.split('.').map(n => parseInt(n, 10));
229
- const latestParts = latest.split('.').map(n => parseInt(n, 10));
230
- for (let i = 0; i < Math.max(currentParts.length, latestParts.length); i++) {
231
- const currentPart = currentParts[i] || 0;
232
- const latestPart = latestParts[i] || 0;
233
- if (currentPart < latestPart)
234
- return false;
235
- if (currentPart > latestPart)
236
- return true;
237
- }
238
- return true; // Versions are equal
239
- }
240
- async function updateGlobalPackageHttp(packageName, version) {
241
- return new Promise((resolve) => {
242
- console.log(chalk_1.default.gray(` Running: npm install -g ${packageName}@${version}`));
243
- // Use npm.cmd on Windows, npm on Unix
244
- const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
245
- const npmProcess = (0, child_process_1.spawn)(npmCommand, ['install', '-g', `${packageName}@${version}`], {
246
- stdio: ['ignore', 'ignore', 'ignore'] // Suppress output for cleaner experience
247
- });
248
- npmProcess.on('close', (code) => {
249
- resolve(code === 0);
250
- });
251
- npmProcess.on('error', () => {
252
- resolve(false);
253
- });
254
- // Timeout after 30 seconds
255
- setTimeout(() => {
256
- npmProcess.kill();
257
- resolve(false);
258
- }, 30000);
259
- });
260
- }
261
150
  exports.syncCommand = new commander_1.Command('sync')
262
151
  .description('Sync workflow stubs from the framework registry')
263
152
  .option('-f, --force', 'Force sync even if digest matches')
264
- .option('--skip-updates', 'Skip checking for CLI updates')
153
+ .option('--skip-updates', 'Skip checking for CLI updates (legacy)')
265
154
  .option('--local', 'Sync from local development server (port derived from git branch)')
266
155
  .action(exports.runSync);
@@ -1,7 +1,4 @@
1
1
  "use strict";
2
- // MCP Server Registry - Builds MCP servers from provider definitions
3
- // This file NO LONGER contains hardcoded server configurations
4
- // All provider MCP server configs come from the provider registry (server or local fallback)
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.BASE_MCP_SERVERS = void 0;
7
4
  exports.buildProviderMCPServer = buildProviderMCPServer;
@@ -324,7 +324,7 @@ const autoConfigureMCP = async (fraimKey, tokenInput, selectedIDEs, providerConf
324
324
  console.log(chalk_1.default.blue('\n🎯 Next steps:'));
325
325
  console.log(chalk_1.default.cyan(' 1. Restart your configured IDEs'));
326
326
  console.log(chalk_1.default.cyan(' 2. Go to any project directory'));
327
- console.log(chalk_1.default.cyan(' 3. Run: fraim init-project'));
327
+ console.log(chalk_1.default.cyan(' 3. Run: npx fraim-framework@latest init-project'));
328
328
  console.log(chalk_1.default.cyan(' 4. Ask your AI agent: "list fraim workflows"'));
329
329
  }
330
330
  };
@@ -2,8 +2,6 @@
2
2
  /**
3
3
  * FRAIM Configuration Types
4
4
  * TypeScript types for .fraim/config.json
5
- *
6
- * Each field includes rich metadata for agent detection and user interaction
7
5
  */
8
6
  Object.defineProperty(exports, "__esModule", { value: true });
9
7
  exports.DEFAULT_FRAIM_CONFIG = void 0;
@@ -243,14 +243,6 @@ class FraimLocalMCPServer {
243
243
  this.machineInfo = null;
244
244
  this.repoInfo = null;
245
245
  this.engine = null;
246
- this.fallbackRewriteCount = 0;
247
- this.fallbackRewriteRequestCount = 0;
248
- this.fallbackRewriteTokens = new Map();
249
- this.pendingFallbackRequestCount = 0;
250
- this.pendingFallbackTokenRewriteCount = 0;
251
- this.pendingFallbackTokenCounts = new Map();
252
- this.pendingFallbackEvents = [];
253
- this.fallbackSummaryEmitted = false;
254
246
  this.writer = writer || process.stdout.write.bind(process.stdout);
255
247
  this.remoteUrl = process.env.FRAIM_REMOTE_URL || 'https://fraim.wellnessatwork.me';
256
248
  this.apiKey = this.loadApiKey();
@@ -1043,7 +1035,6 @@ class FraimLocalMCPServer {
1043
1035
  return response;
1044
1036
  const rewritten = this.rewriteUnresolvedProxyPlaceholders(response.result);
1045
1037
  if (rewritten.tokens.length > 0) {
1046
- this.recordFallbackRewrite(rewritten.tokens);
1047
1038
  this.logError(`[${FraimLocalMCPServer.FALLBACK_ALERT_MARKER}] Rewrote unresolved proxy placeholders to agent placeholders: ${rewritten.tokens.join(', ')}`);
1048
1039
  }
1049
1040
  return {
@@ -1051,79 +1042,6 @@ class FraimLocalMCPServer {
1051
1042
  result: rewritten.value
1052
1043
  };
1053
1044
  }
1054
- recordFallbackRewrite(tokens) {
1055
- this.fallbackRewriteRequestCount += 1;
1056
- this.fallbackRewriteCount += tokens.length;
1057
- for (const token of tokens) {
1058
- const existing = this.fallbackRewriteTokens.get(token) || 0;
1059
- this.fallbackRewriteTokens.set(token, existing + 1);
1060
- }
1061
- this.pendingFallbackRequestCount += 1;
1062
- this.pendingFallbackTokenRewriteCount += tokens.length;
1063
- for (const token of tokens) {
1064
- const existing = this.pendingFallbackTokenCounts.get(token) || 0;
1065
- this.pendingFallbackTokenCounts.set(token, existing + 1);
1066
- }
1067
- this.pendingFallbackEvents.push({
1068
- at: new Date().toISOString(),
1069
- tokenCount: tokens.length,
1070
- tokens: [...tokens]
1071
- });
1072
- if (this.pendingFallbackEvents.length > 250) {
1073
- this.pendingFallbackEvents = this.pendingFallbackEvents.slice(-250);
1074
- }
1075
- }
1076
- buildPendingFallbackTelemetryPayload() {
1077
- if (this.pendingFallbackRequestCount === 0 &&
1078
- this.pendingFallbackTokenRewriteCount === 0 &&
1079
- this.pendingFallbackEvents.length === 0) {
1080
- return null;
1081
- }
1082
- const tokenCounts = {};
1083
- for (const [token, count] of this.pendingFallbackTokenCounts.entries()) {
1084
- tokenCounts[token] = count;
1085
- }
1086
- return {
1087
- requestFallbacks: this.pendingFallbackRequestCount,
1088
- tokenRewrites: this.pendingFallbackTokenRewriteCount,
1089
- tokenCounts,
1090
- events: this.pendingFallbackEvents.map((event) => ({
1091
- at: event.at,
1092
- tokenCount: event.tokenCount,
1093
- tokens: [...event.tokens]
1094
- }))
1095
- };
1096
- }
1097
- acknowledgePendingFallbackTelemetry(payload) {
1098
- this.pendingFallbackRequestCount = Math.max(0, this.pendingFallbackRequestCount - payload.requestFallbacks);
1099
- this.pendingFallbackTokenRewriteCount = Math.max(0, this.pendingFallbackTokenRewriteCount - payload.tokenRewrites);
1100
- for (const [token, sentCount] of Object.entries(payload.tokenCounts)) {
1101
- const current = this.pendingFallbackTokenCounts.get(token) || 0;
1102
- const next = Math.max(0, current - Math.max(0, sentCount));
1103
- if (next === 0) {
1104
- this.pendingFallbackTokenCounts.delete(token);
1105
- }
1106
- else {
1107
- this.pendingFallbackTokenCounts.set(token, next);
1108
- }
1109
- }
1110
- if (payload.events.length > 0) {
1111
- this.pendingFallbackEvents.splice(0, Math.min(payload.events.length, this.pendingFallbackEvents.length));
1112
- }
1113
- }
1114
- emitFallbackSummary(reason) {
1115
- if (this.fallbackSummaryEmitted)
1116
- return;
1117
- this.fallbackSummaryEmitted = true;
1118
- if (this.fallbackRewriteCount === 0)
1119
- return;
1120
- const topTokens = Array.from(this.fallbackRewriteTokens.entries())
1121
- .sort((a, b) => b[1] - a[1])
1122
- .slice(0, 10)
1123
- .map(([token, count]) => `${token}:${count}`)
1124
- .join(', ');
1125
- this.logError(`[${FraimLocalMCPServer.FALLBACK_SUMMARY_MARKER}] reason=${reason} requestFallbacks=${this.fallbackRewriteRequestCount} tokenRewrites=${this.fallbackRewriteCount} uniqueTokens=${this.fallbackRewriteTokens.size}${topTokens ? ` topTokens=${topTokens}` : ''}`);
1126
- }
1127
1045
  extractSessionIdFromRequest(request) {
1128
1046
  const sessionIdFromParams = request.params?.sessionId;
1129
1047
  if (typeof sessionIdFromParams === 'string' && sessionIdFromParams.trim().length > 0) {
@@ -1197,7 +1115,6 @@ class FraimLocalMCPServer {
1197
1115
  * tool calls and recursive inclusion resolution.
1198
1116
  */
1199
1117
  async _doProxyToRemote(request, requestId = (0, crypto_1.randomUUID)()) {
1200
- let sentFallbackTelemetry = null;
1201
1118
  try {
1202
1119
  // Special handling for fraim_connect - automatically inject machine and repo info
1203
1120
  if (request.method === 'tools/call' && request.params?.name === 'fraim_connect') {
@@ -1269,7 +1186,6 @@ class FraimLocalMCPServer {
1269
1186
  // Update the request with injected info
1270
1187
  request.params.arguments = args;
1271
1188
  }
1272
- sentFallbackTelemetry = this.buildPendingFallbackTelemetryPayload();
1273
1189
  const headers = {
1274
1190
  'Content-Type': 'application/json',
1275
1191
  'x-api-key': this.apiKey,
@@ -1280,11 +1196,6 @@ class FraimLocalMCPServer {
1280
1196
  if (sessionId) {
1281
1197
  headers[FraimLocalMCPServer.SESSION_HEADER] = sessionId;
1282
1198
  }
1283
- if (sentFallbackTelemetry) {
1284
- headers[FraimLocalMCPServer.FALLBACK_HEADER] = Buffer
1285
- .from(JSON.stringify(sentFallbackTelemetry), 'utf8')
1286
- .toString('base64');
1287
- }
1288
1199
  this.log(`[req:${requestId}] Proxying ${request.method} to ${this.remoteUrl}/mcp`);
1289
1200
  // Resolve templates in the outgoing request so the remote server
1290
1201
  // only ever sees finalized values.
@@ -1295,9 +1206,6 @@ class FraimLocalMCPServer {
1295
1206
  headers,
1296
1207
  timeout: 30000
1297
1208
  });
1298
- if (sentFallbackTelemetry) {
1299
- this.acknowledgePendingFallbackTelemetry(sentFallbackTelemetry);
1300
- }
1301
1209
  return response.data;
1302
1210
  }
1303
1211
  catch (error) {
@@ -1322,10 +1230,6 @@ class FraimLocalMCPServer {
1322
1230
  localMcpVersion: this.localVersion
1323
1231
  };
1324
1232
  }
1325
- if (sentFallbackTelemetry) {
1326
- // Request reached the server and produced a response; clear sent fallback delta.
1327
- this.acknowledgePendingFallbackTelemetry(sentFallbackTelemetry);
1328
- }
1329
1233
  return forwarded;
1330
1234
  }
1331
1235
  return {
@@ -1652,19 +1556,16 @@ class FraimLocalMCPServer {
1652
1556
  process.stdin.on('end', () => {
1653
1557
  this.log('🛑 Stdin closed, shutting down...');
1654
1558
  cleanup();
1655
- this.emitFallbackSummary('stdin_end');
1656
1559
  process.exit(0);
1657
1560
  });
1658
1561
  process.on('SIGTERM', () => {
1659
1562
  this.log('🛑 SIGTERM received, shutting down...');
1660
1563
  cleanup();
1661
- this.emitFallbackSummary('sigterm');
1662
1564
  process.exit(0);
1663
1565
  });
1664
1566
  process.on('SIGINT', () => {
1665
1567
  this.log('🛑 SIGINT received, shutting down...');
1666
1568
  cleanup();
1667
- this.emitFallbackSummary('sigint');
1668
1569
  process.exit(0);
1669
1570
  });
1670
1571
  this.log('✅ FRAIM Local MCP Server ready');
@@ -1692,8 +1593,6 @@ FraimLocalMCPServer.AGENT_RESOLUTION_NOTICE = [
1692
1593
  'Replace all `agent.*` placeholders with the best value from repository context, user intent, and available project config.'
1693
1594
  ].join('\n');
1694
1595
  FraimLocalMCPServer.FALLBACK_ALERT_MARKER = 'PROXY_FALLBACK_ALERT';
1695
- FraimLocalMCPServer.FALLBACK_SUMMARY_MARKER = 'PROXY_FALLBACK_SUMMARY';
1696
- FraimLocalMCPServer.FALLBACK_HEADER = 'x-fraim-proxy-fallback-telemetry';
1697
1596
  FraimLocalMCPServer.SESSION_HEADER = 'x-fraim-session-id';
1698
1597
  // Start server if run directly
1699
1598
  if (require.main === module) {
@@ -4,7 +4,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.UsageCollector = void 0;
7
+ const mongodb_1 = require("mongodb");
7
8
  const axios_1 = __importDefault(require("axios"));
9
+ // A placeholder ObjectId used when the real API key ID is not yet known.
10
+ // The server will override this with the correct ID from the authenticated API key.
11
+ const PLACEHOLDER_API_KEY_ID = new mongodb_1.ObjectId('000000000000000000000000');
8
12
  /**
9
13
  * Usage event collector for local MCP proxy
10
14
  * Collects usage events and batches them for upload
@@ -24,16 +28,15 @@ class UsageCollector {
24
28
  * Collect MCP tool call event
25
29
  */
26
30
  collectMCPCall(toolName, args, sessionId, success = true, duration) {
27
- if (!this.apiKeyId) {
28
- return; // Silently skip if no API key
29
- }
30
31
  const parsed = this.parseMCPCall(toolName, args);
31
32
  if (!parsed)
32
33
  return;
33
34
  const event = {
34
35
  type: parsed.type,
35
36
  name: parsed.name,
36
- apiKeyId: this.apiKeyId,
37
+ // Use set apiKeyId if available, otherwise a placeholder.
38
+ // The server will override this with the correct value from the auth token.
39
+ apiKeyId: this.apiKeyId || PLACEHOLDER_API_KEY_ID,
37
40
  sessionId,
38
41
  success
39
42
  };
@@ -46,13 +49,12 @@ class UsageCollector {
46
49
  * Collect usage event directly (for backward compatibility with tests)
47
50
  */
48
51
  collectEvent(type, name, sessionId, success = true, duration) {
49
- if (!this.apiKeyId) {
50
- return; // Silently skip if no API key
51
- }
52
52
  const event = {
53
53
  type,
54
54
  name,
55
- apiKeyId: this.apiKeyId,
55
+ // Use set apiKeyId if available, otherwise a placeholder.
56
+ // The server will override this with the correct value from the auth token.
57
+ apiKeyId: this.apiKeyId || PLACEHOLDER_API_KEY_ID,
56
58
  sessionId,
57
59
  success
58
60
  };
package/index.js CHANGED
@@ -36,13 +36,13 @@ function runCLI() {
36
36
  // Solution: On Windows with shell: true, quote paths containing spaces.
37
37
  // On Unix with shell: false, pass the path unquoted (spawnSync handles it correctly).
38
38
  const isWindows = process.platform === 'win32';
39
-
39
+
40
40
  // On Windows with shell, quote paths with spaces to prevent shell misinterpretation
41
41
  // On Unix without shell, pass path as-is (spawnSync handles spaces correctly)
42
- const processedSrcPath = (isWindows && srcPath.includes(' '))
43
- ? `"${srcPath}"`
42
+ const processedSrcPath = (isWindows && srcPath.includes(' '))
43
+ ? `"${srcPath}"`
44
44
  : srcPath;
45
-
45
+
46
46
  const result = spawnSync(
47
47
  'npx',
48
48
  ['tsx', processedSrcPath, ...process.argv.slice(2)],
@@ -66,7 +66,7 @@ function runCLI() {
66
66
  module.exports = {
67
67
  FRAIM_INFO: {
68
68
  name: 'FRAIM',
69
- version: '2.0.54',
69
+ version: '2.0.93',
70
70
  repository: 'https://github.com/mathursrus/FRAIM'
71
71
  }
72
72
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fraim-framework",
3
- "version": "2.0.93",
3
+ "version": "2.0.94",
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": {
@@ -10,10 +10,9 @@
10
10
  "scripts": {
11
11
  "dev": "tsx --watch src/fraim-mcp-server.ts > server.log 2>&1",
12
12
  "dev:prod": "npm run build && node dist/src/fraim-mcp-server.js > server.log 2>&1",
13
- "build": "tsc && npm run build:stubs && npm run build:fraim-brain && npm run build:config-schema && node scripts/copy-registry.js && npm run validate:registry && npm run validate:fraim-pro-assets && tsx scripts/validate-purity.ts",
13
+ "build": "tsc && npm run build:stubs && npm run build:fraim-brain && node scripts/copy-registry.js && node -e \"require('fs').copyFileSync('src/core/types.ts', 'registry/templates/manager/fraim-config-schema.ts')\" && npm run validate:registry && npm run validate:fraim-pro-assets && tsx scripts/validate-purity.ts",
14
14
  "build:stubs": "tsx scripts/build-stub-registry.ts",
15
15
  "build:fraim-brain": "node scripts/generate-fraim-brain.js",
16
- "build:config-schema": "tsx scripts/generate-config-schema-template.ts",
17
16
  "test-all": "npm run test && npm run test:isolated && npm run test:ui",
18
17
  "test": "node scripts/test-with-server.js",
19
18
  "test:isolated": "npx tsx --test --test-reporter=spec tests/isolated/test-*.ts",