- Delete unused dataMerger, formatter, Device model, dead service exports, and the empty agents/ + .gitkeep placeholders. - Extract STORE_MODES and MDM device-type filters into a shared constants.js. - Anchor bot regexes (^help|^store|^analyze) so "analyze store 305" no longer fires both handlers; replace catch-all noise. - Hoist inline require() calls in integrations to top-of-file imports. - Harden WebSocket server: Authorization header support, single-agent enforcement, bounded pending requests, server-level error handler, coalesced cache refresh in Meraki client. - Wrap Meraki/MDM network calls with withRetry; add request timeouts. - Migrate all console.* calls onto utils/logger.js (LOG_LEVEL aware); drive Webex framework logLevel from env. - Refactor storeDetail.js: shared renderClientLine + buildMdmSection helpers cut duplication roughly in half. - Refresh README structure, document LOG_LEVEL, add npm run agent script, add jest testMatch + new tests (handlers, HealthReport, Store, ws). Verified: npm run lint clean, 7 suites / 31 tests passing. Co-authored-by: Cursor <cursoragent@cursor.com>
108 lines
3.2 KiB
JavaScript
108 lines
3.2 KiB
JavaScript
const Framework = require('webex-node-bot-framework');
|
|
const config = require('./config');
|
|
const { startWebSocketServer, stopWebSocketServer } = require('./services/websocket');
|
|
const { handleStoreCommand, handleAnalyzeCommand, handleHelpCommand } = require('./bot/handlers');
|
|
const logger = require('./utils/logger');
|
|
|
|
const framework = new Framework({
|
|
token: config.webex.token,
|
|
removeWebhooksOnStart: true,
|
|
logLevel: config.logLevel,
|
|
});
|
|
|
|
logger.info('Starting NetAnalyzer Framework', { botName: config.webex.name });
|
|
|
|
// Register command handlers (anchored at the start of the message so
|
|
// "please analyze store 305" doesn't fire both store and analyze handlers).
|
|
framework.hears(
|
|
/^\s*help\b/i,
|
|
handleHelpCommand,
|
|
'Show available commands (try: help, help store, help analyze)'
|
|
);
|
|
framework.hears(
|
|
/^\s*store\b/i,
|
|
handleStoreCommand,
|
|
'store <number> — info + network + server\nstore <number> pos — POS devices\nstore <number> ios — iOS devices'
|
|
);
|
|
framework.hears(
|
|
/^\s*analyze\b/i,
|
|
handleAnalyzeCommand,
|
|
'analyze <number> — health summary\nanalyze <number> pos — POS health (broken only)\nanalyze <number> ios — iOS health (broken only)'
|
|
);
|
|
|
|
// Friendly fallback for anything that didn't match the commands above.
|
|
framework.hears(
|
|
/.*/,
|
|
(bot, trigger) => {
|
|
logger.info('Unhandled message', { text: trigger.message.text });
|
|
bot.say(
|
|
'I heard: ' +
|
|
trigger.message.text +
|
|
'\n\nTry `store <number>`, `store <number> pos`, `analyze <number>`, or `help` for commands.'
|
|
);
|
|
},
|
|
99999
|
|
);
|
|
|
|
framework.on('initialized', () => {
|
|
logger.info('Framework initialized and connected via WebSocket');
|
|
});
|
|
|
|
framework.on('spawn', (bot, _id, addedBy) => {
|
|
logger.info('Bot spawned in room', { room: bot.room.title || 'Unknown' });
|
|
if (addedBy) {
|
|
bot.say(
|
|
'NetAnalyzer is ready!\n\nTry `store 782`, `store 782 pos`, `analyze 782`, or type `help` for all commands.'
|
|
);
|
|
}
|
|
});
|
|
|
|
framework
|
|
.start()
|
|
.then(() => {
|
|
logger.info('WebSocket server for remote agent starting');
|
|
startWebSocketServer();
|
|
})
|
|
.catch(err => {
|
|
logger.error('Error starting framework', { error: err.message });
|
|
process.exit(1);
|
|
});
|
|
|
|
// ==================== Graceful Shutdown ====================
|
|
let isShuttingDown = false;
|
|
|
|
async function shutdown(signal) {
|
|
if (isShuttingDown) return;
|
|
isShuttingDown = true;
|
|
|
|
logger.info('Shutting down gracefully', { signal });
|
|
|
|
try {
|
|
stopWebSocketServer();
|
|
|
|
// webex-node-bot-framework may not expose a clean stop; do what we can.
|
|
if (framework && typeof framework.stop === 'function') {
|
|
await framework.stop().catch(() => {});
|
|
}
|
|
|
|
logger.info('Cleanup complete. Exiting.');
|
|
} catch (err) {
|
|
logger.error('Error during shutdown', { error: err.message });
|
|
} finally {
|
|
process.exit(0);
|
|
}
|
|
}
|
|
|
|
process.on('SIGINT', () => shutdown('SIGINT'));
|
|
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
|
|
|
process.on('uncaughtException', err => {
|
|
logger.error('Uncaught Exception', { error: err.message, stack: err.stack });
|
|
shutdown('uncaughtException');
|
|
});
|
|
|
|
process.on('unhandledRejection', reason => {
|
|
logger.error('Unhandled Rejection', {
|
|
reason: reason instanceof Error ? reason.message : String(reason),
|
|
});
|
|
});
|