fraim-framework 2.0.69 → 2.0.71
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/bin/fraim.js +1 -1
- package/dist/src/cli/commands/init-project.js +2 -2
- package/dist/src/cli/commands/init.js +148 -0
- package/dist/src/cli/commands/mcp.js +65 -0
- package/dist/src/cli/commands/setup.js +2 -2
- package/dist/src/cli/commands/sync.js +5 -5
- package/dist/src/cli/commands/wizard.js +35 -0
- package/dist/src/{utils → cli/utils}/version-utils.js +5 -5
- package/dist/src/local-mcp-server/stdio-server.js +2 -2
- package/package.json +12 -13
- package/dist/src/fraim/db-service.js +0 -135
- package/dist/src/fraim/issues.js +0 -69
- package/dist/src/fraim/template-processor.js +0 -138
- package/dist/src/utils/request-utils.js +0 -23
- /package/dist/src/{utils → cli/utils}/digest-utils.js +0 -0
- /package/dist/src/{utils → cli/utils}/platform-detection.js +0 -0
- /package/dist/src/{utils → cli/utils}/remote-sync.js +0 -0
- /package/dist/src/{utils → cli/utils}/script-sync-utils.js +0 -0
- /package/dist/src/{fraim → core}/config-loader.js +0 -0
- /package/dist/src/{fraim → core}/types.js +0 -0
- /package/dist/src/{utils → core/utils}/git-utils.js +0 -0
- /package/dist/src/{utils → core/utils}/object-utils.js +0 -0
- /package/dist/src/{utils → core/utils}/provider-utils.js +0 -0
- /package/dist/src/{utils → core/utils}/stub-generator.js +0 -0
- /package/dist/src/{utils → core/utils}/workflow-parser.js +0 -0
package/bin/fraim.js
CHANGED
|
@@ -10,8 +10,8 @@ const path_1 = __importDefault(require("path"));
|
|
|
10
10
|
const os_1 = __importDefault(require("os"));
|
|
11
11
|
const chalk_1 = __importDefault(require("chalk"));
|
|
12
12
|
const sync_1 = require("./sync");
|
|
13
|
-
const platform_detection_1 = require("
|
|
14
|
-
const version_utils_1 = require("
|
|
13
|
+
const platform_detection_1 = require("../utils/platform-detection");
|
|
14
|
+
const version_utils_1 = require("../utils/version-utils");
|
|
15
15
|
const checkGlobalSetup = () => {
|
|
16
16
|
const fraimUserDir = process.env.FRAIM_USER_DIR || path_1.default.join(os_1.default.homedir(), '.fraim');
|
|
17
17
|
const globalConfigPath = path_1.default.join(fraimUserDir, 'config.json');
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.initCommand = exports.runInit = void 0;
|
|
7
|
+
const commander_1 = require("commander");
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
11
|
+
const first_run_1 = require("../setup/first-run");
|
|
12
|
+
const sync_1 = require("./sync");
|
|
13
|
+
const platform_detection_1 = require("../../utils/platform-detection");
|
|
14
|
+
const version_utils_1 = require("../../utils/version-utils");
|
|
15
|
+
const script_sync_utils_1 = require("../../utils/script-sync-utils");
|
|
16
|
+
const runInit = async (options = {}) => {
|
|
17
|
+
const projectRoot = process.cwd();
|
|
18
|
+
const fraimDir = path_1.default.join(projectRoot, '.fraim');
|
|
19
|
+
const configPath = path_1.default.join(fraimDir, 'config.json');
|
|
20
|
+
console.log(chalk_1.default.blue('🚀 Initializing FRAIM...'));
|
|
21
|
+
if (!fs_1.default.existsSync(fraimDir)) {
|
|
22
|
+
fs_1.default.mkdirSync(fraimDir, { recursive: true });
|
|
23
|
+
console.log(chalk_1.default.green('✅ Created .fraim directory'));
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
console.log(chalk_1.default.yellow('ℹ️ .fraim directory already exists'));
|
|
27
|
+
}
|
|
28
|
+
if (!fs_1.default.existsSync(configPath)) {
|
|
29
|
+
let config;
|
|
30
|
+
// Try to detect platform from git remote
|
|
31
|
+
const detection = (0, platform_detection_1.detectPlatformFromGit)();
|
|
32
|
+
if (options.skipPlatform || detection.provider === 'unknown' || !detection.repository) {
|
|
33
|
+
// Conversational mode - no platform integration
|
|
34
|
+
if (!options.skipPlatform && (detection.provider === 'unknown' || !detection.repository)) {
|
|
35
|
+
console.log(chalk_1.default.yellow('\nℹ️ No git remote found or unsupported platform.'));
|
|
36
|
+
console.log(chalk_1.default.blue(' Initializing in conversational mode (no platform integration).'));
|
|
37
|
+
console.log(chalk_1.default.gray(' You can still use FRAIM workflows and AI features.'));
|
|
38
|
+
console.log(chalk_1.default.gray(' Platform features (issues, PRs) will be unavailable.\n'));
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
console.log(chalk_1.default.blue('\n Initializing in conversational mode (platform integration skipped).\n'));
|
|
42
|
+
}
|
|
43
|
+
// Get project name from directory or git
|
|
44
|
+
let projectName = path_1.default.basename(projectRoot);
|
|
45
|
+
try {
|
|
46
|
+
const { execSync } = require('child_process');
|
|
47
|
+
const gitName = execSync('git config --get remote.origin.url', {
|
|
48
|
+
stdio: 'pipe',
|
|
49
|
+
timeout: 2000
|
|
50
|
+
}).toString().trim();
|
|
51
|
+
const match = gitName.match(/\/([^\/]+?)(\.git)?$/);
|
|
52
|
+
if (match) {
|
|
53
|
+
projectName = match[1];
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch (e) {
|
|
57
|
+
// Use directory name as fallback
|
|
58
|
+
}
|
|
59
|
+
config = {
|
|
60
|
+
version: (0, version_utils_1.getFraimVersion)(),
|
|
61
|
+
project: {
|
|
62
|
+
name: projectName
|
|
63
|
+
},
|
|
64
|
+
customizations: {
|
|
65
|
+
workflowsPath: '.fraim/workflows'
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
console.log(chalk_1.default.gray(` Project: ${projectName}`));
|
|
69
|
+
console.log(chalk_1.default.gray(` Mode: Conversational (no platform integration)`));
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
// Integrated mode - use platform
|
|
73
|
+
if (!detection.repository) {
|
|
74
|
+
console.log(chalk_1.default.red('❌ Error: Repository information not available'));
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
// Validate the detected repository config
|
|
78
|
+
const validation = (0, platform_detection_1.validateRepositoryConfig)(detection.repository);
|
|
79
|
+
if (!validation.valid) {
|
|
80
|
+
console.log(chalk_1.default.red('❌ Error: Invalid repository configuration:'));
|
|
81
|
+
validation.errors.forEach(err => console.log(chalk_1.default.red(` - ${err}`)));
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
console.log(chalk_1.default.blue('\n Initializing in integrated mode (platform integration enabled).'));
|
|
85
|
+
console.log(chalk_1.default.gray(` Platform: ${detection.provider.toUpperCase()}`));
|
|
86
|
+
if (detection.provider === 'github') {
|
|
87
|
+
console.log(chalk_1.default.gray(` Repository: ${detection.repository.owner}/${detection.repository.name}`));
|
|
88
|
+
}
|
|
89
|
+
else if (detection.provider === 'ado') {
|
|
90
|
+
console.log(chalk_1.default.gray(` Organization: ${detection.repository.organization}`));
|
|
91
|
+
console.log(chalk_1.default.gray(` Project: ${detection.repository.project}`));
|
|
92
|
+
console.log(chalk_1.default.gray(` Repository: ${detection.repository.name}`));
|
|
93
|
+
}
|
|
94
|
+
console.log();
|
|
95
|
+
config = {
|
|
96
|
+
version: (0, version_utils_1.getFraimVersion)(),
|
|
97
|
+
project: {
|
|
98
|
+
name: detection.repository.name
|
|
99
|
+
},
|
|
100
|
+
repository: detection.repository,
|
|
101
|
+
customizations: {
|
|
102
|
+
workflowsPath: '.fraim/workflows'
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
fs_1.default.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
107
|
+
console.log(chalk_1.default.green('✅ Created .fraim/config.json'));
|
|
108
|
+
}
|
|
109
|
+
// Create subdirectories
|
|
110
|
+
['workflows'].forEach(dir => {
|
|
111
|
+
const dirPath = path_1.default.join(fraimDir, dir);
|
|
112
|
+
if (!fs_1.default.existsSync(dirPath)) {
|
|
113
|
+
fs_1.default.mkdirSync(dirPath, { recursive: true });
|
|
114
|
+
console.log(chalk_1.default.green(`✅ Created .fraim/${dir}`));
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
console.log(chalk_1.default.blue('\n🎉 FRAIM initialized successfully!'));
|
|
118
|
+
// Sync workflows from registry
|
|
119
|
+
await (0, sync_1.runSync)({});
|
|
120
|
+
// Sync scripts to user directory
|
|
121
|
+
console.log(chalk_1.default.blue('🔄 Syncing FRAIM scripts to user directory...'));
|
|
122
|
+
// Find registry path (same logic as sync command)
|
|
123
|
+
let registryPath = path_1.default.join(__dirname, '../../../../registry');
|
|
124
|
+
if (!fs_1.default.existsSync(registryPath)) {
|
|
125
|
+
registryPath = path_1.default.join(__dirname, '../../../registry');
|
|
126
|
+
}
|
|
127
|
+
if (!fs_1.default.existsSync(registryPath)) {
|
|
128
|
+
registryPath = path_1.default.join(projectRoot, 'registry');
|
|
129
|
+
}
|
|
130
|
+
if (fs_1.default.existsSync(registryPath)) {
|
|
131
|
+
const syncResult = (0, script_sync_utils_1.syncScriptsToUserDirectory)(registryPath);
|
|
132
|
+
console.log(chalk_1.default.green(`✅ Synced ${syncResult.synced} self-contained scripts to user directory.`));
|
|
133
|
+
if (syncResult.ephemeral > 0) {
|
|
134
|
+
console.log(chalk_1.default.gray(` ${syncResult.ephemeral} dependent scripts will use ephemeral execution.`));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
console.log(chalk_1.default.yellow('⚠️ Registry not found, skipping script sync.'));
|
|
139
|
+
}
|
|
140
|
+
// Trigger First Run Experience
|
|
141
|
+
await (0, first_run_1.runFirstRunExperience)();
|
|
142
|
+
process.exit(0);
|
|
143
|
+
};
|
|
144
|
+
exports.runInit = runInit;
|
|
145
|
+
exports.initCommand = new commander_1.Command('init')
|
|
146
|
+
.description('Initialize FRAIM in the current project')
|
|
147
|
+
.option('--skip-platform', 'Skip platform integration (conversational mode only)')
|
|
148
|
+
.action((options) => (0, exports.runInit)(options));
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.mcpCommand = void 0;
|
|
7
|
+
const commander_1 = require("commander");
|
|
8
|
+
const server_1 = require("../../local-mcp-server/server");
|
|
9
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
10
|
+
exports.mcpCommand = new commander_1.Command('mcp')
|
|
11
|
+
.description('Start the local FRAIM MCP server')
|
|
12
|
+
.option('-p, --port <port>', 'Port to run the server on', '3003')
|
|
13
|
+
.option('--remote-only', 'Use remote server only (skip local proxy)', false)
|
|
14
|
+
.action(async (options) => {
|
|
15
|
+
try {
|
|
16
|
+
if (options.remoteOnly) {
|
|
17
|
+
console.log(chalk_1.default.yellow('⚠️ Remote-only mode not yet implemented'));
|
|
18
|
+
console.log(chalk_1.default.blue('ℹ️ Starting local MCP server instead...'));
|
|
19
|
+
}
|
|
20
|
+
const port = parseInt(options.port);
|
|
21
|
+
if (isNaN(port) || port < 1 || port > 65535) {
|
|
22
|
+
console.error(chalk_1.default.red('❌ Invalid port number. Must be between 1 and 65535.'));
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
console.log(chalk_1.default.blue('🚀 Starting FRAIM Local MCP Server...'));
|
|
26
|
+
console.log(chalk_1.default.gray(`📡 Port: ${port}`));
|
|
27
|
+
console.log(chalk_1.default.gray(`🔄 Auto-updates: Every 5 minutes`));
|
|
28
|
+
console.log(chalk_1.default.gray(`📁 Cache: ~/.fraim/cache`));
|
|
29
|
+
console.log('');
|
|
30
|
+
// Set the port in environment
|
|
31
|
+
process.env.FRAIM_LOCAL_MCP_PORT = port.toString();
|
|
32
|
+
// Start the server
|
|
33
|
+
await (0, server_1.startLocalMCPServer)();
|
|
34
|
+
// Server is now running, show configuration instructions
|
|
35
|
+
console.log('');
|
|
36
|
+
console.log(chalk_1.default.green('✅ FRAIM Local MCP Server is running!'));
|
|
37
|
+
console.log('');
|
|
38
|
+
console.log(chalk_1.default.bold('📋 Agent Configuration:'));
|
|
39
|
+
console.log('');
|
|
40
|
+
console.log(chalk_1.default.gray('Add this to your agent\'s MCP configuration:'));
|
|
41
|
+
console.log('');
|
|
42
|
+
console.log(chalk_1.default.cyan(JSON.stringify({
|
|
43
|
+
"mcpServers": {
|
|
44
|
+
"fraim": {
|
|
45
|
+
"command": "fraim",
|
|
46
|
+
"args": ["mcp"],
|
|
47
|
+
"env": {}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}, null, 2)));
|
|
51
|
+
console.log('');
|
|
52
|
+
console.log(chalk_1.default.bold('🔗 Endpoints:'));
|
|
53
|
+
console.log(chalk_1.default.gray(` MCP: http://localhost:${port}/mcp`));
|
|
54
|
+
console.log(chalk_1.default.gray(` Health: http://localhost:${port}/health`));
|
|
55
|
+
console.log('');
|
|
56
|
+
console.log(chalk_1.default.bold('🛑 To stop the server:'));
|
|
57
|
+
console.log(chalk_1.default.gray(' Press Ctrl+C'));
|
|
58
|
+
console.log('');
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
console.error(chalk_1.default.red('❌ Failed to start Local MCP Server:'));
|
|
62
|
+
console.error(chalk_1.default.red(error instanceof Error ? error.message : String(error)));
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
@@ -12,8 +12,8 @@ const path_1 = __importDefault(require("path"));
|
|
|
12
12
|
const os_1 = __importDefault(require("os"));
|
|
13
13
|
const token_validator_1 = require("../setup/token-validator");
|
|
14
14
|
const auto_mcp_setup_1 = require("../setup/auto-mcp-setup");
|
|
15
|
-
const version_utils_1 = require("
|
|
16
|
-
const platform_detection_1 = require("
|
|
15
|
+
const version_utils_1 = require("../utils/version-utils");
|
|
16
|
+
const platform_detection_1 = require("../utils/platform-detection");
|
|
17
17
|
const init_project_1 = require("./init-project");
|
|
18
18
|
const promptForFraimKey = async () => {
|
|
19
19
|
console.log(chalk_1.default.blue('🔑 FRAIM Key Setup'));
|
|
@@ -42,10 +42,10 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
42
42
|
const path_1 = __importDefault(require("path"));
|
|
43
43
|
const chalk_1 = __importDefault(require("chalk"));
|
|
44
44
|
const child_process_1 = require("child_process");
|
|
45
|
-
const config_loader_1 = require("../../
|
|
46
|
-
const version_utils_1 = require("
|
|
47
|
-
const script_sync_utils_1 = require("
|
|
48
|
-
const git_utils_1 = require("../../utils/git-utils");
|
|
45
|
+
const config_loader_1 = require("../../core/config-loader");
|
|
46
|
+
const version_utils_1 = require("../utils/version-utils");
|
|
47
|
+
const script_sync_utils_1 = require("../utils/script-sync-utils");
|
|
48
|
+
const git_utils_1 = require("../../core/utils/git-utils");
|
|
49
49
|
/**
|
|
50
50
|
* Load API key from user-level config (~/.fraim/config.json)
|
|
51
51
|
*/
|
|
@@ -76,7 +76,7 @@ const runSync = async (options) => {
|
|
|
76
76
|
else if (isPostInstall) {
|
|
77
77
|
console.log(chalk_1.default.gray('⏭️ Skipping auto-update check during installation to prevent loops.'));
|
|
78
78
|
}
|
|
79
|
-
const { syncFromRemote } = await Promise.resolve().then(() => __importStar(require('
|
|
79
|
+
const { syncFromRemote } = await Promise.resolve().then(() => __importStar(require('../utils/remote-sync')));
|
|
80
80
|
// Path 1: --local flag → hit localhost MCP server
|
|
81
81
|
if (options.local) {
|
|
82
82
|
console.log(chalk_1.default.blue('🔄 Syncing FRAIM workflows from local server...'));
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.wizardCommand = void 0;
|
|
7
|
+
const commander_1 = require("commander");
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
const init_js_1 = require("./init.js");
|
|
10
|
+
const sync_js_1 = require("./sync.js");
|
|
11
|
+
exports.wizardCommand = new commander_1.Command('wizard')
|
|
12
|
+
.description('Run the interactive setup wizard')
|
|
13
|
+
.action(async () => {
|
|
14
|
+
console.log(chalk_1.default.cyan('🔮 FRAIM Interactive Setup Wizard\n'));
|
|
15
|
+
console.log('Running V2 setup (Init + Sync)...\n');
|
|
16
|
+
try {
|
|
17
|
+
// Run initialization
|
|
18
|
+
try {
|
|
19
|
+
await (0, init_js_1.runInit)();
|
|
20
|
+
}
|
|
21
|
+
catch (err) {
|
|
22
|
+
console.log(chalk_1.default.yellow('⚠️ Init notice:'), err.message);
|
|
23
|
+
}
|
|
24
|
+
console.log(''); // newline
|
|
25
|
+
// Run sync (Force not strictly required if init is fresh, but safe)
|
|
26
|
+
// But if user runs wizard on existing project, maybe they want to resync
|
|
27
|
+
await (0, sync_js_1.runSync)({ force: false });
|
|
28
|
+
console.log(chalk_1.default.blue('\n✨ Wizard completed successfully!'));
|
|
29
|
+
console.log(chalk_1.default.gray('Tip: In the future, you can run `fraim sync` to update workflows.'));
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
console.error(chalk_1.default.red('❌ Wizard failed:'), error);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
@@ -8,12 +8,12 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
9
|
function getFraimVersion() {
|
|
10
10
|
// Try reliable paths to find package.json relative to this file
|
|
11
|
-
// locally: src/utils/version-utils.ts -> package.json is
|
|
12
|
-
// dist: dist/src/utils/version-utils.js -> package.json is
|
|
11
|
+
// locally: src/cli/utils/version-utils.ts -> package.json is ../../../package.json
|
|
12
|
+
// dist: dist/src/cli/utils/version-utils.js -> package.json is ../../../../package.json
|
|
13
13
|
const possiblePaths = [
|
|
14
|
-
path_1.default.join(__dirname, '
|
|
15
|
-
path_1.default.join(__dirname, '
|
|
16
|
-
path_1.default.join(process.cwd(), 'package.json') // Fallback to CWD
|
|
14
|
+
path_1.default.join(__dirname, '../../../package.json'), // Local dev (src)
|
|
15
|
+
path_1.default.join(__dirname, '../../../../package.json'), // Dist (dist/src)
|
|
16
|
+
path_1.default.join(process.cwd(), 'package.json') // Fallback to CWD
|
|
17
17
|
];
|
|
18
18
|
for (const pkgPath of possiblePaths) {
|
|
19
19
|
if (fs_1.default.existsSync(pkgPath)) {
|
|
@@ -25,8 +25,8 @@ const os_1 = require("os");
|
|
|
25
25
|
const child_process_1 = require("child_process");
|
|
26
26
|
const crypto_1 = require("crypto");
|
|
27
27
|
const axios_1 = __importDefault(require("axios"));
|
|
28
|
-
const provider_utils_1 = require("../utils/provider-utils");
|
|
29
|
-
const object_utils_1 = require("../utils/object-utils");
|
|
28
|
+
const provider_utils_1 = require("../core/utils/provider-utils");
|
|
29
|
+
const object_utils_1 = require("../core/utils/object-utils");
|
|
30
30
|
/**
|
|
31
31
|
* Handle template substitution logic separately for better testability
|
|
32
32
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fraim-framework",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.71",
|
|
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,7 +10,7 @@
|
|
|
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 && node scripts/copy-ai-manager-rules.js && npm run validate:registry",
|
|
13
|
+
"build": "tsc && npm run build:stubs && node scripts/copy-ai-manager-rules.js && npm run validate:registry && tsx scripts/validate-purity.ts",
|
|
14
14
|
"build:stubs": "tsx scripts/build-stub-registry.ts",
|
|
15
15
|
"test": "node scripts/test-with-server.js",
|
|
16
16
|
"start:fraim": "tsx src/fraim-mcp-server.ts",
|
|
@@ -67,16 +67,20 @@
|
|
|
67
67
|
"@types/node": "^20.0.0",
|
|
68
68
|
"@types/prompts": "^2.4.9",
|
|
69
69
|
"fast-glob": "^3.3.3",
|
|
70
|
+
"html-to-docx": "^1.8.0",
|
|
71
|
+
"markdown-it": "^14.1.0",
|
|
72
|
+
"markdown-it-highlightjs": "^4.2.0",
|
|
70
73
|
"pptxgenjs": "^4.0.1",
|
|
74
|
+
"puppeteer": "^24.36.1",
|
|
71
75
|
"qrcode": "^1.5.4",
|
|
76
|
+
"sharp": "^0.34.5",
|
|
72
77
|
"tsx": "^4.0.0",
|
|
73
78
|
"typescript": "^5.0.0"
|
|
74
79
|
},
|
|
75
80
|
"files": [
|
|
76
81
|
"dist/src/local-mcp-server/",
|
|
77
82
|
"dist/src/cli/",
|
|
78
|
-
"dist/src/
|
|
79
|
-
"dist/src/utils/",
|
|
83
|
+
"dist/src/core/",
|
|
80
84
|
"bin/fraim.js",
|
|
81
85
|
"bin/fraim-mcp.js",
|
|
82
86
|
"index.js",
|
|
@@ -92,16 +96,11 @@
|
|
|
92
96
|
"axios": "^1.7.0",
|
|
93
97
|
"chalk": "4.1.2",
|
|
94
98
|
"commander": "^14.0.2",
|
|
95
|
-
"cors": "^2.8.5",
|
|
96
99
|
"dotenv": "^16.4.7",
|
|
97
|
-
"express": "^5.2.1",
|
|
98
|
-
"html-to-docx": "^1.8.0",
|
|
99
|
-
"markdown-it": "^14.1.0",
|
|
100
|
-
"markdown-it-highlightjs": "^4.2.0",
|
|
101
|
-
"mongodb": "^7.0.0",
|
|
102
100
|
"prompts": "^2.4.2",
|
|
103
|
-
"
|
|
104
|
-
"
|
|
105
|
-
"
|
|
101
|
+
"tree-kill": "^1.2.2",
|
|
102
|
+
"express": "^5.2.1",
|
|
103
|
+
"cors": "^2.8.5",
|
|
104
|
+
"mongodb": "^7.0.0"
|
|
106
105
|
}
|
|
107
106
|
}
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.FraimDbService = void 0;
|
|
4
|
-
const mongodb_1 = require("mongodb");
|
|
5
|
-
const git_utils_1 = require("../utils/git-utils");
|
|
6
|
-
class FraimDbService {
|
|
7
|
-
constructor() {
|
|
8
|
-
const url = process.env.MONGODB_URI || process.env.MONGO_DATABASE_URL;
|
|
9
|
-
if (!url) {
|
|
10
|
-
throw new Error('MONGODB_URI or MONGO_DATABASE_URL not found in environment');
|
|
11
|
-
}
|
|
12
|
-
this.client = new mongodb_1.MongoClient(url);
|
|
13
|
-
}
|
|
14
|
-
async connect() {
|
|
15
|
-
await this.client.connect();
|
|
16
|
-
const dbName = (0, git_utils_1.determineDatabaseName)();
|
|
17
|
-
this.db = this.client.db(dbName);
|
|
18
|
-
// Use fraim_ prefix for standalone server collections
|
|
19
|
-
this.keysCollection = this.db.collection('fraim_api_keys');
|
|
20
|
-
this.sessionsCollection = this.db.collection('fraim_telemetry_sessions');
|
|
21
|
-
this.signupsCollection = this.db.collection('fraim_website_signups');
|
|
22
|
-
this.salesCollection = this.db.collection('fraim_sales_inquiries');
|
|
23
|
-
// Create indexes
|
|
24
|
-
await this.keysCollection.createIndex({ key: 1 }, { unique: true });
|
|
25
|
-
await this.sessionsCollection.createIndex({ sessionId: 1 }, { unique: true });
|
|
26
|
-
await this.sessionsCollection.createIndex({ userId: 1 });
|
|
27
|
-
await this.sessionsCollection.createIndex({ lastActive: -1 });
|
|
28
|
-
await this.signupsCollection.createIndex({ email: 1 }, { unique: true });
|
|
29
|
-
await this.signupsCollection.createIndex({ timestamp: -1 });
|
|
30
|
-
await this.salesCollection.createIndex({ email: 1 });
|
|
31
|
-
await this.salesCollection.createIndex({ timestamp: -1 });
|
|
32
|
-
console.log(`✅ Connected to Fraim DB: ${dbName}`);
|
|
33
|
-
}
|
|
34
|
-
async createSession(session) {
|
|
35
|
-
if (!this.sessionsCollection)
|
|
36
|
-
throw new Error('DB not connected');
|
|
37
|
-
await this.sessionsCollection.insertOne(session);
|
|
38
|
-
}
|
|
39
|
-
async updateSessionActivity(sessionId, lastActive) {
|
|
40
|
-
if (!this.sessionsCollection)
|
|
41
|
-
return; // Fail silently
|
|
42
|
-
await this.sessionsCollection.updateOne({ sessionId }, { $set: { lastActive } });
|
|
43
|
-
}
|
|
44
|
-
async verifyApiKey(key) {
|
|
45
|
-
if (!this.keysCollection)
|
|
46
|
-
throw new Error('DB not connected');
|
|
47
|
-
const apiKey = await this.keysCollection.findOne({ key, isActive: true });
|
|
48
|
-
return apiKey;
|
|
49
|
-
}
|
|
50
|
-
async getActiveSessionByApiKey(key) {
|
|
51
|
-
if (!this.keysCollection || !this.sessionsCollection)
|
|
52
|
-
throw new Error('DB not connected');
|
|
53
|
-
// 1. Get user for this key
|
|
54
|
-
const apiKeyData = await this.verifyApiKey(key);
|
|
55
|
-
if (!apiKeyData)
|
|
56
|
-
return null;
|
|
57
|
-
// 2. Get latest session for this user (within last 24h)
|
|
58
|
-
const dayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000);
|
|
59
|
-
const session = await this.sessionsCollection.findOne({ userId: apiKeyData.userId, lastActive: { $gt: dayAgo } }, { sort: { lastActive: -1 } });
|
|
60
|
-
return session;
|
|
61
|
-
}
|
|
62
|
-
// ... (rest of methods)
|
|
63
|
-
async listApiKeys() {
|
|
64
|
-
if (!this.keysCollection)
|
|
65
|
-
throw new Error('DB not connected');
|
|
66
|
-
return await this.keysCollection.find({}).toArray();
|
|
67
|
-
}
|
|
68
|
-
async createApiKey(data) {
|
|
69
|
-
if (!this.keysCollection)
|
|
70
|
-
throw new Error('DB not connected');
|
|
71
|
-
await this.keysCollection.insertOne({
|
|
72
|
-
...data,
|
|
73
|
-
isActive: true,
|
|
74
|
-
createdAt: new Date()
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
async revokeApiKey(key) {
|
|
78
|
-
if (!this.keysCollection)
|
|
79
|
-
throw new Error('DB not connected');
|
|
80
|
-
const result = await this.keysCollection.updateOne({ key }, { $set: { isActive: false } });
|
|
81
|
-
return result.matchedCount > 0;
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* Generates a secure API key
|
|
85
|
-
*/
|
|
86
|
-
generateApiKey(userId, orgId) {
|
|
87
|
-
const prefix = 'fraim_';
|
|
88
|
-
const random = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
|
|
89
|
-
return `${prefix}${random}`;
|
|
90
|
-
}
|
|
91
|
-
async createWebsiteSignup(signup) {
|
|
92
|
-
if (!this.signupsCollection)
|
|
93
|
-
throw new Error('DB not connected');
|
|
94
|
-
try {
|
|
95
|
-
await this.signupsCollection.insertOne(signup);
|
|
96
|
-
}
|
|
97
|
-
catch (error) {
|
|
98
|
-
// Handle duplicate email gracefully
|
|
99
|
-
if (error.code === 11000) {
|
|
100
|
-
throw new Error('Email already registered');
|
|
101
|
-
}
|
|
102
|
-
throw error;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
async getWebsiteSignups(limit = 100) {
|
|
106
|
-
if (!this.signupsCollection)
|
|
107
|
-
throw new Error('DB not connected');
|
|
108
|
-
return await this.signupsCollection.find({})
|
|
109
|
-
.sort({ timestamp: -1 })
|
|
110
|
-
.limit(limit)
|
|
111
|
-
.toArray();
|
|
112
|
-
}
|
|
113
|
-
async getSignupByEmail(email) {
|
|
114
|
-
if (!this.signupsCollection)
|
|
115
|
-
throw new Error('DB not connected');
|
|
116
|
-
return await this.signupsCollection.findOne({ email });
|
|
117
|
-
}
|
|
118
|
-
async createSalesInquiry(inquiry) {
|
|
119
|
-
if (!this.salesCollection)
|
|
120
|
-
throw new Error('DB not connected');
|
|
121
|
-
await this.salesCollection.insertOne(inquiry);
|
|
122
|
-
}
|
|
123
|
-
async getSalesInquiries(limit = 100) {
|
|
124
|
-
if (!this.salesCollection)
|
|
125
|
-
throw new Error('DB not connected');
|
|
126
|
-
return await this.salesCollection.find({})
|
|
127
|
-
.sort({ timestamp: -1 })
|
|
128
|
-
.limit(limit)
|
|
129
|
-
.toArray();
|
|
130
|
-
}
|
|
131
|
-
async close() {
|
|
132
|
-
await this.client.close();
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
exports.FraimDbService = FraimDbService;
|
package/dist/src/fraim/issues.js
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.fileFraimIssue = fileFraimIssue;
|
|
7
|
-
const axios_1 = __importDefault(require("axios"));
|
|
8
|
-
/**
|
|
9
|
-
* File an issue in the FRAIM repository
|
|
10
|
-
*
|
|
11
|
-
* This function creates issues in the FRAIM repository for bug reports,
|
|
12
|
-
* feature requests, and workflow contributions.
|
|
13
|
-
*/
|
|
14
|
-
async function fileFraimIssue(params) {
|
|
15
|
-
const { title, body, labels, dryRun } = params;
|
|
16
|
-
// Always target the FRAIM repository
|
|
17
|
-
const owner = 'mathursrus';
|
|
18
|
-
const repo = 'FRAIM';
|
|
19
|
-
if (dryRun) {
|
|
20
|
-
return {
|
|
21
|
-
success: true,
|
|
22
|
-
dryRun: true,
|
|
23
|
-
message: `[DRY RUN] Would create GitHub issue: "${title}" in ${owner}/${repo}.`
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
try {
|
|
27
|
-
// Check for GitHub token
|
|
28
|
-
const token = process.env.GITHUB_TOKEN;
|
|
29
|
-
if (!token) {
|
|
30
|
-
return {
|
|
31
|
-
success: false,
|
|
32
|
-
message: `GitHub integration requires GITHUB_TOKEN environment variable.
|
|
33
|
-
|
|
34
|
-
Please set GITHUB_TOKEN environment variable to file issues in ${owner}/${repo}.`
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
// Create the issue in FRAIM repository
|
|
38
|
-
const url = `https://api.github.com/repos/${owner}/${repo}/issues`;
|
|
39
|
-
const payload = { title, body };
|
|
40
|
-
if (labels && labels.length > 0) {
|
|
41
|
-
payload.labels = labels;
|
|
42
|
-
}
|
|
43
|
-
const response = await axios_1.default.post(url, payload, {
|
|
44
|
-
headers: {
|
|
45
|
-
'Authorization': `Bearer ${token}`,
|
|
46
|
-
'Accept': 'application/vnd.github.v3+json',
|
|
47
|
-
'Content-Type': 'application/json',
|
|
48
|
-
},
|
|
49
|
-
});
|
|
50
|
-
return {
|
|
51
|
-
success: true,
|
|
52
|
-
issueNumber: response.data.number,
|
|
53
|
-
htmlUrl: response.data.html_url
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
catch (error) {
|
|
57
|
-
let errorMessage = 'Unknown error';
|
|
58
|
-
if (axios_1.default.isAxiosError(error)) {
|
|
59
|
-
errorMessage = `Status: ${error.response?.status} - ${JSON.stringify(error.response?.data)}`;
|
|
60
|
-
}
|
|
61
|
-
else if (error instanceof Error) {
|
|
62
|
-
errorMessage = error.message;
|
|
63
|
-
}
|
|
64
|
-
return {
|
|
65
|
-
success: false,
|
|
66
|
-
message: `Failed to create issue in FRAIM repository: ${errorMessage}`
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
}
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.TemplateEngine = void 0;
|
|
4
|
-
exports.getTemplateEngine = getTemplateEngine;
|
|
5
|
-
const fs_1 = require("fs");
|
|
6
|
-
const path_1 = require("path");
|
|
7
|
-
const provider_utils_1 = require("../utils/provider-utils");
|
|
8
|
-
const object_utils_1 = require("../utils/object-utils");
|
|
9
|
-
/**
|
|
10
|
-
* Multi-Provider Template Engine
|
|
11
|
-
* Processes workflow templates with provider-specific action mappings
|
|
12
|
-
*/
|
|
13
|
-
class TemplateEngine {
|
|
14
|
-
constructor(registryPath) {
|
|
15
|
-
this.providers = new Map();
|
|
16
|
-
this.registryPath = registryPath || (0, path_1.join)(__dirname, '../../registry');
|
|
17
|
-
this.loadProviders();
|
|
18
|
-
}
|
|
19
|
-
loadProviders() {
|
|
20
|
-
try {
|
|
21
|
-
// Load GitHub provider templates
|
|
22
|
-
const githubPath = (0, path_1.join)(this.registryPath, 'providers/github.json');
|
|
23
|
-
if ((0, fs_1.existsSync)(githubPath)) {
|
|
24
|
-
const githubTemplates = JSON.parse((0, fs_1.readFileSync)(githubPath, 'utf-8'));
|
|
25
|
-
this.providers.set('github', githubTemplates);
|
|
26
|
-
}
|
|
27
|
-
// Load ADO provider templates
|
|
28
|
-
const adoPath = (0, path_1.join)(this.registryPath, 'providers/ado.json');
|
|
29
|
-
if ((0, fs_1.existsSync)(adoPath)) {
|
|
30
|
-
const adoTemplates = JSON.parse((0, fs_1.readFileSync)(adoPath, 'utf-8'));
|
|
31
|
-
this.providers.set('ado', adoTemplates);
|
|
32
|
-
}
|
|
33
|
-
console.log(`✅ Loaded ${this.providers.size} provider templates`);
|
|
34
|
-
}
|
|
35
|
-
catch (error) {
|
|
36
|
-
console.warn('⚠️ Failed to load provider templates:', error);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* Process workflow content with provider actions only
|
|
41
|
-
*/
|
|
42
|
-
processWorkflow(workflowContent, repositoryInfo) {
|
|
43
|
-
// Only process provider actions, no client-side config variables
|
|
44
|
-
return this.processProviderActions(workflowContent, repositoryInfo);
|
|
45
|
-
}
|
|
46
|
-
processProviderActions(workflowContent, repositoryInfo) {
|
|
47
|
-
// Determine provider from repository info using shared utility
|
|
48
|
-
const repoData = repositoryInfo?.repository || repositoryInfo;
|
|
49
|
-
const provider = repoData?.provider || (0, provider_utils_1.detectProvider)(repoData?.url);
|
|
50
|
-
const providerTemplates = this.providers.get(provider);
|
|
51
|
-
if (!providerTemplates) {
|
|
52
|
-
console.warn(`⚠️ No templates found for provider: ${provider}, falling back to GitHub`);
|
|
53
|
-
return this.processWithProvider(workflowContent, 'github', repositoryInfo);
|
|
54
|
-
}
|
|
55
|
-
return this.processWithProvider(workflowContent, provider, repositoryInfo);
|
|
56
|
-
}
|
|
57
|
-
processWithProvider(workflowContent, provider, repositoryInfo) {
|
|
58
|
-
const providerTemplates = this.providers.get(provider);
|
|
59
|
-
if (!providerTemplates) {
|
|
60
|
-
return workflowContent; // Return unchanged if no templates
|
|
61
|
-
}
|
|
62
|
-
let processed = workflowContent;
|
|
63
|
-
// Replace {{action}} with provider-specific implementations
|
|
64
|
-
for (const [action, template] of Object.entries(providerTemplates)) {
|
|
65
|
-
const regex = new RegExp(`{{${action}}}`, 'g');
|
|
66
|
-
const renderedTemplate = this.renderTemplate(template, repositoryInfo);
|
|
67
|
-
processed = processed.replace(regex, renderedTemplate);
|
|
68
|
-
}
|
|
69
|
-
return processed;
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* Render template with repository variable substitution
|
|
73
|
-
*/
|
|
74
|
-
renderTemplate(template, repositoryInfo) {
|
|
75
|
-
if (!repositoryInfo)
|
|
76
|
-
return template;
|
|
77
|
-
return template.replace(/{{([^}]+)}}/g, (match, path) => {
|
|
78
|
-
const trimmedPath = path.trim();
|
|
79
|
-
// Handle repository.* variables
|
|
80
|
-
if (trimmedPath.startsWith('repository.')) {
|
|
81
|
-
const repoPath = trimmedPath.substring('repository.'.length);
|
|
82
|
-
// Check if repositoryInfo has a 'repository' property (full config)
|
|
83
|
-
// or if it IS the repository data directly
|
|
84
|
-
let repoData = repositoryInfo.repository || repositoryInfo;
|
|
85
|
-
const value = (0, object_utils_1.getNestedValue)(repoData, repoPath);
|
|
86
|
-
return value !== undefined ? String(value) : match;
|
|
87
|
-
}
|
|
88
|
-
// Handle direct variables (for backward compatibility)
|
|
89
|
-
const value = (0, object_utils_1.getNestedValue)(repositoryInfo, trimmedPath);
|
|
90
|
-
return value !== undefined ? String(value) : match;
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
/**
|
|
94
|
-
* Get available providers
|
|
95
|
-
*/
|
|
96
|
-
getAvailableProviders() {
|
|
97
|
-
return Array.from(this.providers.keys());
|
|
98
|
-
}
|
|
99
|
-
/**
|
|
100
|
-
* Get available actions for a provider
|
|
101
|
-
*/
|
|
102
|
-
getProviderActions(provider) {
|
|
103
|
-
const templates = this.providers.get(provider);
|
|
104
|
-
return templates ? Object.keys(templates) : [];
|
|
105
|
-
}
|
|
106
|
-
/**
|
|
107
|
-
* Validate that all template actions in content are supported
|
|
108
|
-
*/
|
|
109
|
-
validateWorkflow(workflowContent, provider) {
|
|
110
|
-
const actionRegex = /{{(\w+)}}/g;
|
|
111
|
-
const actions = new Set();
|
|
112
|
-
let match;
|
|
113
|
-
while ((match = actionRegex.exec(workflowContent)) !== null) {
|
|
114
|
-
// Skip config variables
|
|
115
|
-
if (!match[1].startsWith('config.')) {
|
|
116
|
-
actions.add(match[1]);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
const providerActions = this.getProviderActions(provider);
|
|
120
|
-
const missingActions = Array.from(actions).filter(action => !providerActions.includes(action));
|
|
121
|
-
return {
|
|
122
|
-
valid: missingActions.length === 0,
|
|
123
|
-
missingActions
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
exports.TemplateEngine = TemplateEngine;
|
|
128
|
-
// Global template engine instance
|
|
129
|
-
let globalTemplateEngine = null;
|
|
130
|
-
/**
|
|
131
|
-
* Get or create global template engine instance
|
|
132
|
-
*/
|
|
133
|
-
function getTemplateEngine(registryPath) {
|
|
134
|
-
if (!globalTemplateEngine || (registryPath && registryPath !== globalTemplateEngine['registryPath'])) {
|
|
135
|
-
globalTemplateEngine = new TemplateEngine(registryPath);
|
|
136
|
-
}
|
|
137
|
-
return globalTemplateEngine;
|
|
138
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.EMAIL_REGEX = void 0;
|
|
4
|
-
exports.validateEmail = validateEmail;
|
|
5
|
-
exports.getRequestMeta = getRequestMeta;
|
|
6
|
-
/**
|
|
7
|
-
* Standard email validation regex
|
|
8
|
-
*/
|
|
9
|
-
exports.EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
10
|
-
/**
|
|
11
|
-
* Validate an email address
|
|
12
|
-
*/
|
|
13
|
-
function validateEmail(email) {
|
|
14
|
-
return exports.EMAIL_REGEX.test(email);
|
|
15
|
-
}
|
|
16
|
-
/**
|
|
17
|
-
* Extract IP and UserAgent from an Express Request
|
|
18
|
-
*/
|
|
19
|
-
function getRequestMeta(req) {
|
|
20
|
-
const ip = req.headers['x-forwarded-for'] || req.ip || 'unknown';
|
|
21
|
-
const userAgent = req.headers['user-agent'] || 'unknown';
|
|
22
|
-
return { ip, userAgent };
|
|
23
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|