create NLP command for details
This commit is contained in:
parent
31d6296c6e
commit
181eade8c4
6 changed files with 348 additions and 10 deletions
29
src/app.js
29
src/app.js
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* Main application file for Fylgja Slack bot
|
||||
* Initializes the Slack Bolt app with custom ExpressReceiver Registers command handlers
|
||||
*
|
||||
* Now supports the universal /fylgja command
|
||||
*/
|
||||
const { App, ExpressReceiver } = require('@slack/bolt');
|
||||
const fs = require('fs');
|
||||
|
@ -13,16 +13,14 @@ const { SIGMA_CLI_PATH, SIGMA_CLI_CONFIG, SLACK_CONFIG } = require('./config/app
|
|||
const { getFileName } = require('./utils/file_utils');
|
||||
const FILE_NAME = getFileName(__filename);
|
||||
|
||||
// Import individual command handlers
|
||||
// Import the unified fylgja command handler
|
||||
const fylgjaCommandHandler = require('./handlers/fylgja_command_handler');
|
||||
|
||||
const sigmaDetailsHandler = require('./handlers/sigma/sigma_details_handler');
|
||||
const sigmaSearchHandler = require('./handlers/sigma/sigma_search_handler');
|
||||
const sigmaCreateHandler = require('./handlers/sigma/sigma_create_handler');
|
||||
// Import the action registry
|
||||
const sigmaActionRegistry = require('./handlers/sigma/actions/sigma_action_registry');
|
||||
//const configCommand = require('./commands/config/index.js');
|
||||
//const alertsCommand = require('./commands/alerts/index.js');
|
||||
//const caseCommand = require('./commands/case/index.js');
|
||||
//const statsCommand = require('./commands/stats/index.js');
|
||||
|
||||
// Verify sigma-cli is installed
|
||||
if (!fs.existsSync(SIGMA_CLI_PATH)) {
|
||||
|
@ -49,8 +47,23 @@ const app = new App({
|
|||
receiver: expressReceiver
|
||||
});
|
||||
|
||||
// Register individual command handlers for all sigma commands
|
||||
logger.info('Registering command handlers');
|
||||
// Register the unified fylgja command handler
|
||||
logger.info('Registering unified fylgja command handler');
|
||||
app.command('/fylgja', async ({ command, ack, respond }) => {
|
||||
try {
|
||||
await ack();
|
||||
logger.info(`Received fylgja command: ${command.text}`);
|
||||
await fylgjaCommandHandler.handleCommand(command, respond);
|
||||
} catch (error) {
|
||||
logger.error(`Error handling fylgja command: ${error.message}`);
|
||||
logger.debug(`Error stack: ${error.stack}`);
|
||||
await respond({
|
||||
text: `An error occurred: ${error.message}`,
|
||||
response_type: 'ephemeral'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Register sigma command handlers directly
|
||||
app.command('/sigma-create', async ({ command, ack, respond }) => {
|
||||
|
|
142
src/handlers/fylgja_command_handler.js
Normal file
142
src/handlers/fylgja_command_handler.js
Normal file
|
@ -0,0 +1,142 @@
|
|||
/**
|
||||
* fylgja_command_handler.js
|
||||
*
|
||||
* Unified command handler for the Fylgja Slack bot.
|
||||
* Processes natural language commands and routes to appropriate handlers.
|
||||
*/
|
||||
const logger = require('../utils/logger');
|
||||
const { handleError } = require('../utils/error_handler');
|
||||
const FILE_NAME = 'fylgja_command_handler.js';
|
||||
|
||||
// Import command handlers
|
||||
const sigmaDetailsHandler = require('./sigma/sigma_details_handler');
|
||||
const sigmaSearchHandler = require('./sigma/sigma_search_handler');
|
||||
const sigmaCreateHandler = require('./sigma/sigma_create_handler');
|
||||
const sigmaStatsHandler = require('./sigma/sigma_stats_handler');
|
||||
|
||||
// Import language processing utilities
|
||||
const commandParser = require('../lang/command_parser');
|
||||
|
||||
/**
|
||||
* Handle the universal fylgja command
|
||||
*
|
||||
* @param {Object} command - The Slack command object
|
||||
* @param {Function} respond - Function to send response back to Slack
|
||||
*/
|
||||
const handleCommand = async (command, respond) => {
|
||||
try {
|
||||
if (!command || !command.text) {
|
||||
logger.warn(`${FILE_NAME}: Empty command received for fylgja`);
|
||||
await respond({
|
||||
text: 'Please provide a command. Try `/fylgja help` for available commands.',
|
||||
response_type: 'ephemeral'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info(`${FILE_NAME}: Processing fylgja command: ${command.text}`);
|
||||
|
||||
// Parse the natural language command
|
||||
const parsedCommand = await commandParser.parseCommand(command.text);
|
||||
|
||||
if (!parsedCommand.success) {
|
||||
logger.warn(`${FILE_NAME}: Failed to parse command: ${command.text}`);
|
||||
await respond({
|
||||
text: `I couldn't understand that command. ${parsedCommand.message || ''}`,
|
||||
response_type: 'ephemeral'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Route to the appropriate handler based on the parsed command
|
||||
await routeCommand(parsedCommand.command, command, respond);
|
||||
|
||||
} catch (error) {
|
||||
await handleError(error, `${FILE_NAME}: Command handler`, respond, {
|
||||
responseType: 'ephemeral'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Route the command to the appropriate handler
|
||||
*
|
||||
* @param {Object} parsedCommand - The parsed command object
|
||||
* @param {Object} originalCommand - The original Slack command object
|
||||
* @param {Function} respond - Function to send response back to Slack
|
||||
*/
|
||||
const routeCommand = async (parsedCommand, originalCommand, respond) => {
|
||||
const { action, module, params } = parsedCommand;
|
||||
|
||||
// Create a modified command object with the extracted parameters
|
||||
const modifiedCommand = {
|
||||
...originalCommand,
|
||||
text: params.join(' ')
|
||||
};
|
||||
|
||||
// Log the routing decision
|
||||
logger.debug(`${FILE_NAME}: Routing command - Action: ${action}, Module: ${module}, Params: ${JSON.stringify(params)}`);
|
||||
|
||||
// Route to the appropriate handler
|
||||
switch (`${module}:${action}`) {
|
||||
case 'sigma:details':
|
||||
case 'sigma:explain':
|
||||
await sigmaDetailsHandler.handleCommand(modifiedCommand, respond);
|
||||
break;
|
||||
|
||||
case 'sigma:search':
|
||||
await sigmaSearchHandler.handleCommand(modifiedCommand, respond);
|
||||
break;
|
||||
|
||||
case 'sigma:create':
|
||||
await sigmaCreateHandler.handleCommand(modifiedCommand, respond);
|
||||
break;
|
||||
|
||||
case 'sigma:stats':
|
||||
await sigmaStatsHandler.handleCommand(modifiedCommand, respond);
|
||||
break;
|
||||
|
||||
case 'help:general':
|
||||
await showHelp(respond);
|
||||
break;
|
||||
|
||||
default:
|
||||
logger.warn(`${FILE_NAME}: Unknown command combination: ${module}:${action}`);
|
||||
await respond({
|
||||
text: `I don't know how to ${action} in ${module}. Try \`/fylgja help\` for available commands.`,
|
||||
response_type: 'ephemeral'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Show help information
|
||||
*
|
||||
* @param {Function} respond - Function to send response back to Slack
|
||||
*/
|
||||
const showHelp = async (respond) => {
|
||||
await respond({
|
||||
text: "Here are some example commands you can use with Fylgja:",
|
||||
blocks: [
|
||||
{
|
||||
type: "section",
|
||||
text: {
|
||||
type: "mrkdwn",
|
||||
text: "*Fylgja Commands*\nHere are some example commands you can use:"
|
||||
}
|
||||
},
|
||||
{
|
||||
type: "section",
|
||||
text: {
|
||||
type: "mrkdwn",
|
||||
text: "• `/fylgja explain rule from sigma where id=<rule_id>`\n• `/fylgja search sigma for <query>`\n• `/fylgja create rule in sigma with <parameters>`\n• `/fylgja show stats for sigma`"
|
||||
}
|
||||
}
|
||||
],
|
||||
response_type: 'ephemeral'
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
handleCommand
|
||||
};
|
|
@ -7,7 +7,7 @@
|
|||
const logger = require('../../utils/logger');
|
||||
const { handleError } = require('../../utils/error_handler');
|
||||
const { getSigmaStats } = require('../../services/sigma/sigma_stats_service');
|
||||
const { getStatsBlocks } = require('../../blocks/sigma_stats_block');
|
||||
const { getStatsBlocks } = require('../../blocks/sigma/sigma_stats_block');
|
||||
|
||||
const { getFileName } = require('../../utils/file_utils');
|
||||
const FILE_NAME = getFileName(__filename);
|
||||
|
|
105
src/lang/command_parser.js
Normal file
105
src/lang/command_parser.js
Normal file
|
@ -0,0 +1,105 @@
|
|||
/**
|
||||
* command_parser.js
|
||||
*
|
||||
* Provides functionality for parsing commands for the Fylgja bot
|
||||
*/
|
||||
const logger = require('../utils/logger');
|
||||
const FILE_NAME = 'command_parser.js';
|
||||
|
||||
// Import language patterns and synonyms
|
||||
const commandPatterns = require('./command_patterns');
|
||||
|
||||
/**
|
||||
* Parse a natural language command into a structured command object
|
||||
*
|
||||
* @param {string} commandText - The natural language command text
|
||||
* @returns {Promise<Object>} Result object with success flag and parsed command or error message
|
||||
*/
|
||||
const parseCommand = async (commandText) => {
|
||||
try {
|
||||
logger.debug(`${FILE_NAME}: Parsing command: ${commandText}`);
|
||||
|
||||
if (!commandText || typeof commandText !== 'string') {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Empty or invalid command.'
|
||||
};
|
||||
}
|
||||
|
||||
// Convert to lowercase for case-insensitive matching
|
||||
const normalizedCommand = commandText.toLowerCase().trim();
|
||||
|
||||
// TODO
|
||||
// Handle help command separately
|
||||
if (normalizedCommand === 'help') {
|
||||
return {
|
||||
success: true,
|
||||
command: {
|
||||
action: 'general',
|
||||
module: 'help',
|
||||
params: []
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Try to match command against known patterns
|
||||
for (const pattern of commandPatterns) {
|
||||
const match = matchPattern(normalizedCommand, pattern);
|
||||
if (match) {
|
||||
logger.debug(`${FILE_NAME}: Command matched pattern: ${pattern.name}`);
|
||||
return {
|
||||
success: true,
|
||||
command: match
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// If we reach here, no pattern matched
|
||||
logger.warn(`${FILE_NAME}: No pattern matched for command: ${commandText}`);
|
||||
return {
|
||||
success: false,
|
||||
message: "I couldn't understand that command. Try `/fylgja help` for examples."
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error(`${FILE_NAME}: Error parsing command: ${error.message}`);
|
||||
logger.debug(`${FILE_NAME}: Error stack: ${error.stack}`);
|
||||
return {
|
||||
success: false,
|
||||
message: `Error parsing command: ${error.message}`
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Match a command against a pattern
|
||||
*
|
||||
* @param {string} command - The normalized command text
|
||||
* @param {Object} pattern - The pattern object to match against
|
||||
* @returns {Object|null} Parsed command object or null if no match
|
||||
*/
|
||||
const matchPattern = (command, pattern) => {
|
||||
// Check if the command matches the regex pattern
|
||||
const match = pattern.regex.exec(command);
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Extract parameters based on the pattern's parameter mapping
|
||||
const params = [];
|
||||
for (const paramIndex of pattern.params) {
|
||||
if (match[paramIndex]) {
|
||||
params.push(match[paramIndex].trim());
|
||||
}
|
||||
}
|
||||
|
||||
// Return the structured command
|
||||
return {
|
||||
action: pattern.action,
|
||||
module: pattern.module,
|
||||
params
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
parseCommand
|
||||
};
|
78
src/lang/command_patterns.js
Normal file
78
src/lang/command_patterns.js
Normal file
|
@ -0,0 +1,78 @@
|
|||
/**
|
||||
* command_patterns.js
|
||||
*
|
||||
* Defines pattern matching rules for natural language commands
|
||||
* Each pattern includes a regex and mapping for parameter extraction
|
||||
*/
|
||||
|
||||
/**
|
||||
* Command patterns array
|
||||
* Each pattern object contains:
|
||||
* - name: A descriptive name for the pattern
|
||||
* - regex: A regular expression to match the command
|
||||
* - action: The action to perform (e.g., details, search)
|
||||
* - module: The module to use (e.g., sigma, alerts)
|
||||
* - params: Array of capturing group indices to extract parameters
|
||||
*/
|
||||
const commandPatterns = [
|
||||
// Sigma details patterns
|
||||
{
|
||||
name: 'sigma-details-direct',
|
||||
regex: /^(explain|get|show|display|details|info|about)\s+(rule|detection)\s+(from\s+)?sigma\s+(where\s+)?(id=|id\s+is\s+|with\s+id\s+)(.+)$/i,
|
||||
action: 'details',
|
||||
module: 'sigma',
|
||||
params: [6] // rule ID is in capturing group 6
|
||||
},
|
||||
{
|
||||
name: 'sigma-details-simple',
|
||||
regex: /^(details|explain)\s+(.+)$/i,
|
||||
action: 'details',
|
||||
module: 'sigma',
|
||||
params: [2] // rule ID is in capturing group 2
|
||||
},
|
||||
|
||||
// Sigma search patterns
|
||||
{
|
||||
name: 'sigma-search',
|
||||
regex: /^(search|find|look\s+for)\s+(rules|detections)?\s*(in|from)?\s*sigma\s+(for|where|with)?\s+(.+)$/i,
|
||||
action: 'search',
|
||||
module: 'sigma',
|
||||
params: [5] // search query is in capturing group 5
|
||||
},
|
||||
{
|
||||
name: 'sigma-search-simple',
|
||||
regex: /^(search|find)\s+(.+)$/i,
|
||||
action: 'search',
|
||||
module: 'sigma',
|
||||
params: [2] // search query is in capturing group 2
|
||||
},
|
||||
|
||||
// Sigma create patterns
|
||||
{
|
||||
name: 'sigma-create',
|
||||
regex: /^(create|new|add)\s+(rule|detection)\s+(in|to|for)?\s*sigma\s+(with|using)?\s+(.+)$/i,
|
||||
action: 'create',
|
||||
module: 'sigma',
|
||||
params: [5] // creation parameters in capturing group 5
|
||||
},
|
||||
|
||||
// Sigma stats patterns
|
||||
{
|
||||
name: 'sigma-stats',
|
||||
regex: /^(stats|statistics|metrics|counts)\s+(for|about|on|of)?\s*sigma$/i,
|
||||
action: 'stats',
|
||||
module: 'sigma',
|
||||
params: []
|
||||
},
|
||||
{
|
||||
name: 'sigma-stats-show',
|
||||
regex: /^(show|get|display)\s+(stats|statistics|metrics|counts)\s+(for|about|on|of)?\s*sigma$/i,
|
||||
action: 'stats',
|
||||
module: 'sigma',
|
||||
params: []
|
||||
}
|
||||
|
||||
// Additional command patterns for other modules can be added here
|
||||
];
|
||||
|
||||
module.exports = commandPatterns;
|
Loading…
Add table
Add a link
Reference in a new issue