refactor CLI and decouple from handlers
This commit is contained in:
parent
1b8ba03c8b
commit
f90a08dcde
2 changed files with 272 additions and 181 deletions
|
@ -8,16 +8,7 @@ const { parseCommand } = require('../lang/command_parser');
|
|||
const logger = require('../utils/logger');
|
||||
const { generateGradientLogo } = require('./utils/cli_logo');
|
||||
const outputManager = require('./cli_output_manager');
|
||||
|
||||
// Import command handlers
|
||||
const sigmaSearchHandler = require('../handlers/sigma/sigma_search_entry_handler');
|
||||
const sigmaDetailsHandler = require('../handlers/sigma/sigma_details_handler');
|
||||
const sigmaStatsHandler = require('../handlers/sigma/sigma_stats_handler');
|
||||
const sigmaCreateHandler = require('../handlers/sigma/sigma_create_handler');
|
||||
const { handleCommand: handleAlerts } = require('../handlers/alerts/alerts_handler');
|
||||
const { handleCommand: handleCase } = require('../handlers/case/case_handler');
|
||||
const { handleCommand: handleConfig } = require('../handlers/config/config_handler');
|
||||
const { handleCommand: handleStats } = require('../handlers/stats/stats_handler');
|
||||
const handlerRegistry = require('../handlers/handler_registry');
|
||||
|
||||
// Set constants
|
||||
const FILE_NAME = 'cli.js';
|
||||
|
@ -43,13 +34,8 @@ const rl = readline.createInterface({
|
|||
prompt: 'fylgja> '
|
||||
});
|
||||
|
||||
/**
|
||||
* Command auto-completion function
|
||||
* @param {string} line Current command line input
|
||||
* @returns {Array} Array with possible completions and the substring being completed
|
||||
*/
|
||||
function completer(line) {
|
||||
const commands = [
|
||||
// Available commands for auto-completion
|
||||
const availableCommands = [
|
||||
'search sigma',
|
||||
'details sigma',
|
||||
'stats sigma',
|
||||
|
@ -71,8 +57,14 @@ function completer(line) {
|
|||
'clear'
|
||||
];
|
||||
|
||||
const hits = commands.filter((c) => c.startsWith(line));
|
||||
return [hits.length ? hits : commands, line];
|
||||
/**
|
||||
* Command auto-completion function
|
||||
* @param {string} line Current command line input
|
||||
* @returns {Array} Array with possible completions and the substring being completed
|
||||
*/
|
||||
function completer(line) {
|
||||
const hits = availableCommands.filter((c) => c.startsWith(line));
|
||||
return [hits.length ? hits : availableCommands, line];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -98,14 +90,39 @@ function extractSearchKeywords(input) {
|
|||
return input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a standardized command context object
|
||||
* @param {string} text - Command text
|
||||
* @param {string} module - Module name
|
||||
* @param {string} action - Action name
|
||||
* @param {Array} params - Command parameters
|
||||
* @returns {Object} Command context object
|
||||
*/
|
||||
function createCommandContext(text, module, action, params) {
|
||||
return {
|
||||
command: {
|
||||
text,
|
||||
user_id: 'cli_user',
|
||||
user_name: 'cli_user',
|
||||
command: '/fylgja',
|
||||
channel_id: 'cli',
|
||||
channel_name: 'cli'
|
||||
},
|
||||
meta: {
|
||||
module,
|
||||
action,
|
||||
params,
|
||||
source: 'cli'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a custom respond function for handlers
|
||||
* @param {string} action - The action being performed
|
||||
* @param {string} module - The module handling the action
|
||||
* @param {Array} params - The parameters for the action
|
||||
* @param {Object} context - Command execution context
|
||||
* @returns {Function} A respond function for handler callbacks
|
||||
*/
|
||||
function createRespondFunction(action, module, params) {
|
||||
function createRespondFunction(context) {
|
||||
// Keep track of whether we're waiting for results
|
||||
let isWaitingForResults = false;
|
||||
|
||||
|
@ -132,12 +149,15 @@ function createRespondFunction(action, module, params) {
|
|||
|
||||
// Check for the responseData property (directly from service)
|
||||
if (response.responseData) {
|
||||
const { module, action } = context.meta;
|
||||
|
||||
// Display data based on module and action type
|
||||
if (module === 'sigma') {
|
||||
if (action === 'search' || action === 'complexSearch') {
|
||||
if (['search', 'complexSearch'].includes(action)) {
|
||||
// Convert array response to expected format if needed
|
||||
let dataToFormat = response.responseData;
|
||||
|
||||
// Wrap responseData array in proper structure
|
||||
// Wrap responseData array in proper structure if needed
|
||||
if (Array.isArray(dataToFormat)) {
|
||||
dataToFormat = {
|
||||
results: dataToFormat,
|
||||
|
@ -171,6 +191,118 @@ function createRespondFunction(action, module, params) {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle built-in CLI commands
|
||||
* @param {string} input User input command
|
||||
* @returns {boolean} True if command was handled, false otherwise
|
||||
*/
|
||||
function handleBuiltInCommands(input) {
|
||||
const trimmedInput = input.trim().toLowerCase();
|
||||
|
||||
if (trimmedInput === 'exit' || trimmedInput === 'quit') {
|
||||
outputManager.displaySuccess('Goodbye!');
|
||||
rl.close();
|
||||
process.exit(0);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (trimmedInput === 'clear') {
|
||||
console.clear();
|
||||
rl.prompt();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (trimmedInput === 'help') {
|
||||
outputManager.displayHelp();
|
||||
rl.prompt();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle simple search commands
|
||||
* @param {string} input User input command
|
||||
* @returns {boolean} True if command was handled, false otherwise
|
||||
*/
|
||||
async function handleSimpleSearch(input) {
|
||||
const match = input.trim().match(/^search\s+sigma\s+(.+)$/i);
|
||||
|
||||
if (match && !input.trim().toLowerCase().includes('where') && !input.trim().toLowerCase().includes('with')) {
|
||||
const keyword = match[1];
|
||||
const context = createCommandContext(keyword, 'sigma', 'search', [keyword]);
|
||||
const respond = createRespondFunction(context);
|
||||
|
||||
console.log(`Executing: module=sigma, action=search, params=[${keyword}]`);
|
||||
|
||||
try {
|
||||
// Get handler from registry
|
||||
const handler = handlerRegistry.getHandler('sigma', 'search');
|
||||
if (handler) {
|
||||
await handler.handleCommand(context.command, respond);
|
||||
} else {
|
||||
outputManager.displayError('Handler not found for sigma search');
|
||||
rl.prompt();
|
||||
}
|
||||
} catch (error) {
|
||||
outputManager.displayError(error.message);
|
||||
logger.error(`${FILE_NAME}: Command execution error: ${error.message}`);
|
||||
logger.debug(`${FILE_NAME}: Error stack: ${error.stack}`);
|
||||
rl.prompt();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle complex search commands
|
||||
* @param {string} input User input command
|
||||
* @returns {boolean} True if command was handled, false otherwise
|
||||
*/
|
||||
async function handleComplexSearch(input) {
|
||||
const complexSearchMatch = input.trim().match(/^search\s+sigma\s+(rules\s+|detections\s+)?(where|with)\s+(.+)$/i);
|
||||
|
||||
if (complexSearchMatch) {
|
||||
const complexQuery = complexSearchMatch[3];
|
||||
const searchTerms = extractSearchKeywords(complexQuery);
|
||||
|
||||
const context = createCommandContext(
|
||||
searchTerms || complexQuery,
|
||||
'sigma',
|
||||
'complexSearch',
|
||||
[complexQuery]
|
||||
);
|
||||
|
||||
const respond = createRespondFunction(context);
|
||||
|
||||
console.log(`Executing: module=sigma, action=complexSearch, params=[${complexQuery}]`);
|
||||
|
||||
try {
|
||||
// Get handler from registry
|
||||
const handler = handlerRegistry.getHandler('sigma', 'search');
|
||||
if (handler) {
|
||||
await handler.handleCommand(context.command, respond);
|
||||
} else {
|
||||
outputManager.displayError('Handler not found for sigma complex search');
|
||||
rl.prompt();
|
||||
}
|
||||
} catch (error) {
|
||||
outputManager.displayError(error.message);
|
||||
logger.error(`${FILE_NAME}: Command execution error: ${error.message}`);
|
||||
logger.debug(`${FILE_NAME}: Error stack: ${error.stack}`);
|
||||
rl.prompt();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a command from the CLI
|
||||
* @param {string} input User input command
|
||||
|
@ -183,93 +315,26 @@ async function processCommand(input) {
|
|||
return;
|
||||
}
|
||||
|
||||
// Special CLI commands
|
||||
if (input.trim().toLowerCase() === 'exit' || input.trim().toLowerCase() === 'quit') {
|
||||
outputManager.displaySuccess('Goodbye!');
|
||||
rl.close();
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (input.trim().toLowerCase() === 'clear') {
|
||||
console.clear();
|
||||
rl.prompt();
|
||||
return;
|
||||
}
|
||||
|
||||
if (input.trim().toLowerCase() === 'help') {
|
||||
outputManager.displayHelp();
|
||||
rl.prompt();
|
||||
return;
|
||||
}
|
||||
|
||||
// Add to command history
|
||||
commandHistory.push(input);
|
||||
historyIndex = commandHistory.length;
|
||||
|
||||
// Special case for simple search
|
||||
if (input.trim().match(/^search\s+sigma\s+(.+)$/i) && !input.trim().toLowerCase().includes('where') && !input.trim().toLowerCase().includes('with')) {
|
||||
const keyword = input.trim().match(/^search\s+sigma\s+(.+)$/i)[1];
|
||||
|
||||
// Create fake command object
|
||||
const command = {
|
||||
text: keyword,
|
||||
user_id: 'cli_user',
|
||||
user_name: 'cli_user',
|
||||
command: '/fylgja',
|
||||
channel_id: 'cli',
|
||||
channel_name: 'cli'
|
||||
};
|
||||
|
||||
// Create custom respond function
|
||||
const respond = createRespondFunction('search', 'sigma', [keyword]);
|
||||
|
||||
console.log(`Executing: module=sigma, action=search, params=[${keyword}]`);
|
||||
|
||||
try {
|
||||
await sigmaSearchHandler.handleCommand(command, respond);
|
||||
} catch (error) {
|
||||
outputManager.displayError(error.message);
|
||||
logger.error(`${FILE_NAME}: Command execution error: ${error.message}`);
|
||||
logger.debug(`${FILE_NAME}: Error stack: ${error.stack}`);
|
||||
rl.prompt();
|
||||
}
|
||||
|
||||
// Handle built-in commands
|
||||
if (handleBuiltInCommands(input)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Special case for complex search
|
||||
const complexSearchMatch = input.trim().match(/^search\s+sigma\s+(rules\s+|detections\s+)?(where|with)\s+(.+)$/i);
|
||||
if (complexSearchMatch) {
|
||||
const complexQuery = complexSearchMatch[3];
|
||||
|
||||
// Create fake command object
|
||||
const command = {
|
||||
text: complexQuery,
|
||||
user_id: 'cli_user',
|
||||
user_name: 'cli_user',
|
||||
command: '/fylgja',
|
||||
channel_id: 'cli',
|
||||
channel_name: 'cli'
|
||||
};
|
||||
|
||||
// Create custom respond function
|
||||
const respond = createRespondFunction('complexSearch', 'sigma', [complexQuery]);
|
||||
|
||||
console.log(`Executing: module=sigma, action=complexSearch, params=[${complexQuery}]`);
|
||||
|
||||
try {
|
||||
await sigmaSearchHandler.handleCommand(command, respond);
|
||||
} catch (error) {
|
||||
outputManager.displayError(error.message);
|
||||
logger.error(`${FILE_NAME}: Command execution error: ${error.message}`);
|
||||
logger.debug(`${FILE_NAME}: Error stack: ${error.stack}`);
|
||||
rl.prompt();
|
||||
}
|
||||
|
||||
// Handle simple search
|
||||
if (await handleSimpleSearch(input)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse command using existing parser
|
||||
// Handle complex search
|
||||
if (await handleComplexSearch(input)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse command using existing parser for everything else
|
||||
const parsedCommand = await parseCommand(input);
|
||||
|
||||
if (!parsedCommand.success) {
|
||||
|
@ -284,71 +349,25 @@ async function processCommand(input) {
|
|||
// Only show execution info to the user, not sending to logger
|
||||
console.log(`Executing: module=${module}, action=${action}, params=[${params}]`);
|
||||
|
||||
// Create fake command object similar to Slack's
|
||||
const command = {
|
||||
text: Array.isArray(params) && params.length > 0 ? params[0] : input,
|
||||
user_id: 'cli_user',
|
||||
user_name: 'cli_user',
|
||||
command: '/fylgja',
|
||||
channel_id: 'cli',
|
||||
channel_name: 'cli'
|
||||
};
|
||||
|
||||
// Special handling for complexSearch to extract keywords
|
||||
if (action === 'complexSearch' && module === 'sigma' && params.length > 0) {
|
||||
// Try to extract keywords from complex queries
|
||||
const searchTerms = extractSearchKeywords(params[0]);
|
||||
command.text = searchTerms || params[0];
|
||||
}
|
||||
// Create command context
|
||||
const context = createCommandContext(
|
||||
Array.isArray(params) && params.length > 0 ? params[0] : input,
|
||||
module,
|
||||
action,
|
||||
params
|
||||
);
|
||||
|
||||
// Create custom respond function for CLI
|
||||
const respond = createRespondFunction(action, module, params);
|
||||
const respond = createRespondFunction(context);
|
||||
|
||||
try {
|
||||
switch (module) {
|
||||
case 'sigma':
|
||||
switch (action) {
|
||||
case 'search':
|
||||
case 'complexSearch':
|
||||
await sigmaSearchHandler.handleCommand(command, respond);
|
||||
break;
|
||||
// Get handler from registry
|
||||
const handler = handlerRegistry.getHandler(module, action);
|
||||
|
||||
case 'details':
|
||||
await sigmaDetailsHandler.handleCommand(command, respond);
|
||||
break;
|
||||
|
||||
case 'stats':
|
||||
await sigmaStatsHandler.handleCommand(command, respond);
|
||||
break;
|
||||
|
||||
case 'create':
|
||||
await sigmaCreateHandler.handleCommand(command, respond);
|
||||
break;
|
||||
|
||||
default:
|
||||
outputManager.displayWarning(`Unknown Sigma action: ${action}`);
|
||||
rl.prompt();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'alerts':
|
||||
await handleAlerts(command, respond);
|
||||
break;
|
||||
|
||||
case 'case':
|
||||
await handleCase(command, respond);
|
||||
break;
|
||||
|
||||
case 'config':
|
||||
await handleConfig(command, respond);
|
||||
break;
|
||||
|
||||
case 'stats':
|
||||
await handleStats(command, respond);
|
||||
break;
|
||||
|
||||
default:
|
||||
outputManager.displayWarning(`Unknown module: ${module}`);
|
||||
if (handler) {
|
||||
await handler.handleCommand(context.command, respond);
|
||||
} else {
|
||||
outputManager.displayWarning(`Unknown handler for ${module}.${action}`);
|
||||
rl.prompt();
|
||||
}
|
||||
} catch (error) {
|
||||
|
@ -432,4 +451,3 @@ if (require.main === module) {
|
|||
startCLI
|
||||
};
|
||||
}
|
||||
|
||||
|
|
73
src/handlers/handler_registry.js
Normal file
73
src/handlers/handler_registry.js
Normal file
|
@ -0,0 +1,73 @@
|
|||
/**
|
||||
* handler_registry.js
|
||||
*
|
||||
* Centralized registry for command handlers
|
||||
* Decouples the CLI from specific handler implementations
|
||||
*/
|
||||
const logger = require('../utils/logger');
|
||||
const FILE_NAME = 'handler_registry.js';
|
||||
|
||||
// Create a registry to store handlers
|
||||
const handlers = {};
|
||||
|
||||
/**
|
||||
* Registers a handler for a specific module and action
|
||||
* @param {string} module - The module name (e.g., 'sigma')
|
||||
* @param {string} action - The action name (e.g., 'search')
|
||||
* @param {Object} handler - The handler object with a handleCommand method
|
||||
*/
|
||||
function registerHandler(module, action, handler) {
|
||||
const key = `${module}:${action}`;
|
||||
|
||||
if (!handler || typeof handler.handleCommand !== 'function') {
|
||||
logger.error(`${FILE_NAME}: Invalid handler for ${key}. Must have handleCommand method.`);
|
||||
return;
|
||||
}
|
||||
|
||||
handlers[key] = handler;
|
||||
logger.debug(`${FILE_NAME}: Registered handler for ${key}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a handler for a specific module and action
|
||||
* @param {string} module - The module name (e.g., 'sigma')
|
||||
* @param {string} action - The action name (e.g., 'search')
|
||||
* @returns {Object|null} The handler object or null if not found
|
||||
*/
|
||||
function getHandler(module, action) {
|
||||
const key = `${module}:${action}`;
|
||||
return handlers[key] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the handler registry with all available handlers
|
||||
*/
|
||||
function initializeRegistry() {
|
||||
try {
|
||||
// Import all handlers
|
||||
const sigmaSearchHandler = require('../handlers/sigma/sigma_search_entry_handler');
|
||||
const sigmaDetailsHandler = require('../handlers/sigma/sigma_details_handler');
|
||||
const sigmaStatsHandler = require('../handlers/sigma/sigma_stats_handler');
|
||||
const sigmaCreateHandler = require('../handlers/sigma/sigma_create_handler');
|
||||
|
||||
// Register Sigma handlers
|
||||
registerHandler('sigma', 'search', sigmaSearchHandler);
|
||||
registerHandler('sigma', 'complexSearch', sigmaSearchHandler);
|
||||
registerHandler('sigma', 'details', sigmaDetailsHandler);
|
||||
registerHandler('sigma', 'stats', sigmaStatsHandler);
|
||||
registerHandler('sigma', 'create', sigmaCreateHandler);
|
||||
|
||||
logger.info(`${FILE_NAME}: Handler registry initialized successfully`);
|
||||
} catch (error) {
|
||||
logger.error(`${FILE_NAME}: Error initializing handler registry: ${error.message}`);
|
||||
logger.debug(`${FILE_NAME}: Error stack: ${error.stack}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the registry when the module is loaded
|
||||
initializeRegistry();
|
||||
|
||||
module.exports = {
|
||||
registerHandler,
|
||||
getHandler
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue