netanalyzer/bot/handlers.js
Joseph McQueen 0a46d25bd6 chore: project cleanup — dead code, logging, tests, WS hardening
- 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>
2026-06-24 17:21:22 -04:00

147 lines
5.2 KiB
JavaScript

const { getStoreDetail } = require('../integrations/storeDetail');
const { getStoreHealth } = require('../integrations/storeHealth');
const { parseStoreNumber } = require('../utils/validate');
const { STORE_MODES } = require('../constants');
const logger = require('../utils/logger');
function parseStoreCommand(text) {
const storeNumber = parseStoreNumber(text);
if (!storeNumber) return { storeNumber: null, mode: null };
const lower = text.toLowerCase();
let mode = STORE_MODES.DEFAULT;
if (lower.includes(' pos')) mode = STORE_MODES.POS;
else if (lower.includes(' ios') || lower.includes(' iphone')) mode = STORE_MODES.IOS;
return { storeNumber, mode };
}
async function handleStoreCommand(bot, trigger) {
const { storeNumber, mode } = parseStoreCommand(trigger.message.text);
if (!storeNumber) {
return bot.say(
'markdown',
'Usage: `store <number>`\n\nExample: `store 305`\n\nOptions:\n• `store 305` — info + network + server\n• `store 305 pos` — POS systems\n• `store 305 ios` — iOS devices\n\nType `help store` for more details.'
);
}
const modeLabel =
mode === STORE_MODES.POS ? 'POS' : mode === STORE_MODES.IOS ? 'iOS' : 'Overview';
bot.say('markdown', `🔍 Analyzing Store **${storeNumber}** (${modeLabel})...`);
try {
const report = await getStoreDetail(storeNumber, mode);
const sections = report.split(/\n\n(?=\*\*)/);
for (let i = 0; i < sections.length; i++) {
const section = sections[i].trim();
if (section) {
await bot.say('markdown', section);
if (i < sections.length - 1) await new Promise(r => setTimeout(r, 400));
}
}
} catch (err) {
logger.error('Store analysis error', { storeNumber, error: err.message });
bot.say('markdown', `❌ Error analyzing store **${storeNumber}**:\n${err.message}`);
}
}
async function handleAnalyzeCommand(bot, trigger) {
const { storeNumber, mode } = parseStoreCommand(trigger.message.text);
if (!storeNumber) {
return bot.say(
'markdown',
'Usage: `analyze <number>`\n\nExample: `analyze 305`\n\nOptions:\n• `analyze 305` — full health summary\n• `analyze 305 pos` — POS health (broken only)\n• `analyze 305 ios` — iOS health (broken only)\n\nType `help analyze` for more details.'
);
}
const modeLabel =
mode === STORE_MODES.POS ? 'POS' : mode === STORE_MODES.IOS ? 'iOS' : 'Overview';
bot.say('markdown', `🔍 Running Health Analysis for Store **${storeNumber}** (${modeLabel})...`);
try {
const health = await getStoreHealth(storeNumber, mode);
if (health && health.summary) {
let output = health.summary;
// For sub-modes or analyze, only show broken components
if (mode !== STORE_MODES.DEFAULT) {
const lines = output.split('\n');
const issueStart = lines.findIndex(l => l.includes('Issues Detected') || l.includes('⚠️'));
if (issueStart > -1) {
output =
lines.slice(0, issueStart + 1).join('\n') +
'\n' +
lines
.slice(issueStart + 1)
.filter(l => l.trim().startsWith('-'))
.join('\n');
}
}
bot.say('markdown', output);
} else {
bot.say('markdown', '✅ Analysis completed, but no summary was generated.');
}
} catch (err) {
logger.error('Store health analysis error', { storeNumber, error: err.message });
bot.say('markdown', `❌ Error analyzing store **${storeNumber}**:\n${err.message}`);
}
}
async function handleHelpCommand(bot, trigger) {
const text = trigger.message.text.toLowerCase().trim();
let response;
if (text.includes('store')) {
response = `**Store Commands**
\`store <number>\` — Store info/details, active network devices, and the store server
\`store <number> pos\` — Store server, registers, mobile registers, printers, and payment terminals
\`store <number> ios\` — All iOS devices
**Tip:** Type the command without a number for usage examples.`;
} else if (text.includes('analyze')) {
response = `**Analyze Commands** (shows only broken components for sub-modes)
\`analyze <number>\` — Overall health summary
\`analyze <number> pos\` — POS health (only issues)
\`analyze <number> ios\` — iOS health (only issues)
**Tip:** Type the command without a number for usage examples.`;
} else {
response = `**NetAnalyzer Help**
Available Commands:
**Store Commands:**
\`store <number>\` — Store info/details, active network devices, and the store server
\`store <number> pos\` — Store server, registers, mobile registers, printers, and payment terminals
\`store <number> ios\` — All iOS devices
**Analyze Commands:**
\`analyze <number>\` — Overall health summary
\`analyze <number> pos\` — POS health (broken components only)
\`analyze <number> ios\` — iOS health (broken components only)
**Tips:**
• Most commands work in both group spaces and 1:1 chats.
• Type a command without a number for usage examples.
• Use \`help store\` or \`help analyze\` for more details.
• Try \`store 782\` or \`analyze 782\` to get started.`;
}
bot.say('markdown', response);
}
module.exports = {
handleStoreCommand,
handleAnalyzeCommand,
handleHelpCommand,
parseStoreCommand,
};