mirror of
https://github.com/KeygraphHQ/shannon.git
synced 2026-02-12 17:22:50 +00:00
chore: remove ~500 lines of dead code identified by knip
Remove unused files and exports to improve codebase maintainability: Phase 1 - Deleted files (5): - login_resources/generate-totp-standalone.mjs (replaced by MCP tool) - mcp-server/src/tools/index.js (unused barrel export) - mcp-server/src/utils/index.js (unused barrel export) - mcp-server/src/validation/index.js (unused barrel export) - src/agent-status.js (deprecated 309-line status manager) Phase 2 - Removed unused exports (3): - mcp-server/src/index.js: shannonHelperServer constant - mcp-server/src/utils/error-formatter.js: createFileSystemError function - src/utils/git-manager.js: cleanWorkspace (now internal-only) Phase 3 - Unexported internal functions (4): - src/checkpoint-manager.js: runSingleAgent, runAgentRange, runParallelVuln, runParallelExploit (internal use only) All Shannon CLI commands tested and verified working. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,131 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { createHmac } from 'crypto';
|
||||
|
||||
/**
|
||||
* Standalone TOTP generator that doesn't require external dependencies
|
||||
* Based on RFC 6238 (TOTP: Time-Based One-Time Password Algorithm)
|
||||
*/
|
||||
|
||||
function parseArgs() {
|
||||
const args = {};
|
||||
for (let i = 2; i < process.argv.length; i++) {
|
||||
if (process.argv[i] === '--secret' && i + 1 < process.argv.length) {
|
||||
args.secret = process.argv[i + 1];
|
||||
i++; // Skip the next argument since it's the value
|
||||
} else if (process.argv[i] === '--help' || process.argv[i] === '-h') {
|
||||
args.help = true;
|
||||
}
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
function showHelp() {
|
||||
console.log(`
|
||||
Usage: node generate-totp-standalone.mjs --secret <TOTP_SECRET>
|
||||
|
||||
Generate a Time-based One-Time Password (TOTP) from a secret key.
|
||||
This standalone version doesn't require external dependencies.
|
||||
|
||||
Options:
|
||||
--secret <secret> The base32-encoded TOTP secret key (required)
|
||||
--help, -h Show this help message
|
||||
|
||||
Examples:
|
||||
node generate-totp-standalone.mjs --secret "JBSWY3DPEHPK3PXP"
|
||||
node generate-totp-standalone.mjs --secret "u4e2ewg3d6w7gya3p7plgkef6zgfzo23"
|
||||
|
||||
Output:
|
||||
A 6-digit TOTP code (e.g., 123456)
|
||||
`);
|
||||
}
|
||||
|
||||
// Base32 decoding function
|
||||
function base32Decode(encoded) {
|
||||
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
|
||||
const cleanInput = encoded.toUpperCase().replace(/[^A-Z2-7]/g, '');
|
||||
|
||||
if (cleanInput.length === 0) {
|
||||
return Buffer.alloc(0);
|
||||
}
|
||||
|
||||
const output = [];
|
||||
let bits = 0;
|
||||
let value = 0;
|
||||
|
||||
for (const char of cleanInput) {
|
||||
const index = alphabet.indexOf(char);
|
||||
if (index === -1) {
|
||||
throw new Error(`Invalid base32 character: ${char}`);
|
||||
}
|
||||
|
||||
value = (value << 5) | index;
|
||||
bits += 5;
|
||||
|
||||
if (bits >= 8) {
|
||||
output.push((value >>> (bits - 8)) & 255);
|
||||
bits -= 8;
|
||||
}
|
||||
}
|
||||
|
||||
return Buffer.from(output);
|
||||
}
|
||||
|
||||
// HOTP implementation (RFC 4226)
|
||||
function generateHOTP(secret, counter, digits = 6) {
|
||||
const key = base32Decode(secret);
|
||||
|
||||
// Convert counter to 8-byte buffer (big-endian)
|
||||
const counterBuffer = Buffer.alloc(8);
|
||||
counterBuffer.writeBigUInt64BE(BigInt(counter));
|
||||
|
||||
// Generate HMAC-SHA1
|
||||
const hmac = createHmac('sha1', key);
|
||||
hmac.update(counterBuffer);
|
||||
const hash = hmac.digest();
|
||||
|
||||
// Dynamic truncation
|
||||
const offset = hash[hash.length - 1] & 0x0f;
|
||||
const code = (
|
||||
((hash[offset] & 0x7f) << 24) |
|
||||
((hash[offset + 1] & 0xff) << 16) |
|
||||
((hash[offset + 2] & 0xff) << 8) |
|
||||
(hash[offset + 3] & 0xff)
|
||||
);
|
||||
|
||||
// Generate digits
|
||||
const otp = (code % Math.pow(10, digits)).toString().padStart(digits, '0');
|
||||
return otp;
|
||||
}
|
||||
|
||||
// TOTP implementation (RFC 6238)
|
||||
function generateTOTP(secret, timeStep = 30, digits = 6) {
|
||||
const currentTime = Math.floor(Date.now() / 1000);
|
||||
const counter = Math.floor(currentTime / timeStep);
|
||||
return generateHOTP(secret, counter, digits);
|
||||
}
|
||||
|
||||
function main() {
|
||||
const args = parseArgs();
|
||||
|
||||
if (args.help) {
|
||||
showHelp();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!args.secret) {
|
||||
console.error('Error: --secret parameter is required');
|
||||
console.error('Use --help for usage information');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
const totpCode = generateTOTP(args.secret);
|
||||
console.log(totpCode);
|
||||
} catch (error) {
|
||||
console.error(`Error: ${error.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -28,16 +28,6 @@ export function createShannonHelperServer(targetDir) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Legacy export for backward compatibility
|
||||
* @deprecated Use createShannonHelperServer(targetDir) instead
|
||||
*/
|
||||
export const shannonHelperServer = createSdkMcpServer({
|
||||
name: 'shannon-helper',
|
||||
version: '1.0.0',
|
||||
tools: [saveDeliverableTool, generateTotpTool],
|
||||
});
|
||||
|
||||
// Export tools for direct usage if needed
|
||||
export { saveDeliverableTool, generateTotpTool };
|
||||
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* MCP Tools barrel export
|
||||
*/
|
||||
|
||||
export * from './save-deliverable.js';
|
||||
export * from './generate-totp.js';
|
||||
@@ -31,24 +31,6 @@ export function createValidationError(message, retryable = true, context) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a file system error response
|
||||
*
|
||||
* @param {string} message
|
||||
* @param {boolean} [retryable=false]
|
||||
* @param {Record<string, unknown>} [context]
|
||||
* @returns {ErrorResponse}
|
||||
*/
|
||||
export function createFileSystemError(message, retryable = false, context) {
|
||||
return {
|
||||
status: 'error',
|
||||
message,
|
||||
errorType: 'FileSystemError',
|
||||
retryable,
|
||||
context,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a crypto error response
|
||||
*
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* Utilities barrel export
|
||||
*/
|
||||
|
||||
export * from './file-operations.js';
|
||||
export * from './error-formatter.js';
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* Validation layer barrel export
|
||||
*/
|
||||
|
||||
export * from './queue-validator.js';
|
||||
export * from './totp-validator.js';
|
||||
@@ -1,309 +0,0 @@
|
||||
import chalk from 'chalk';
|
||||
import { path } from 'zx';
|
||||
|
||||
export class AgentStatusManager {
|
||||
constructor(options = {}) {
|
||||
this.mode = options.mode || 'parallel'; // 'parallel' or 'single'
|
||||
this.activeStatuses = new Map();
|
||||
this.lastStatusLine = '';
|
||||
this.hiddenOperationCount = 0;
|
||||
this.lastSummaryCount = 0;
|
||||
this.summaryInterval = options.summaryInterval || 10;
|
||||
this.showTodos = options.showTodos !== false;
|
||||
|
||||
// Tools to completely hide in output
|
||||
this.suppressedTools = new Set([
|
||||
'Read', 'Write', 'Edit', 'MultiEdit',
|
||||
'Grep', 'Glob', 'LS'
|
||||
]);
|
||||
|
||||
// Tools that might be noisy bash commands to hide
|
||||
this.hiddenBashCommands = new Set([
|
||||
'pwd', 'echo', 'ls', 'cd'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update status for an agent based on its current turn data
|
||||
*/
|
||||
updateAgentStatus(agentName, turnData) {
|
||||
if (this.mode === 'single') {
|
||||
this.handleSingleAgentOutput(agentName, turnData);
|
||||
} else {
|
||||
const status = this.extractMeaningfulStatus(turnData);
|
||||
if (status) {
|
||||
this.activeStatuses.set(agentName, status);
|
||||
this.redrawStatusLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle output for single agent mode with clean formatting
|
||||
*/
|
||||
handleSingleAgentOutput(agentName, turnData) {
|
||||
const toolUse = turnData.tool_use;
|
||||
const text = turnData.assistant_text;
|
||||
const turnCount = turnData.turnCount;
|
||||
|
||||
// Check if this is a tool we should hide
|
||||
if (toolUse && this.shouldHideTool(toolUse)) {
|
||||
this.hiddenOperationCount++;
|
||||
|
||||
// Show summary every N hidden operations
|
||||
if (this.hiddenOperationCount - this.lastSummaryCount >= this.summaryInterval) {
|
||||
const operationCount = this.hiddenOperationCount - this.lastSummaryCount;
|
||||
console.log(chalk.gray(` [${operationCount} file operations...]`));
|
||||
this.lastSummaryCount = this.hiddenOperationCount;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Format and show meaningful tools
|
||||
if (toolUse) {
|
||||
const formatted = this.formatMeaningfulTool(toolUse);
|
||||
if (formatted) {
|
||||
console.log(`🤖 ${formatted}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// For turns without tool use, just ignore them silently
|
||||
// These are planning/thinking turns that don't need any output
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a tool should be hidden from output
|
||||
*/
|
||||
shouldHideTool(toolUse) {
|
||||
const toolName = toolUse.name;
|
||||
|
||||
// Always hide these tools
|
||||
if (this.suppressedTools.has(toolName)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Hide TodoWrite unless we're configured to show todos
|
||||
if (toolName === 'TodoWrite' && !this.showTodos) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Hide simple bash commands
|
||||
if (toolName === 'Bash') {
|
||||
const command = toolUse.input?.command || '';
|
||||
const simpleCommand = command.split(' ')[0];
|
||||
return this.hiddenBashCommands.has(simpleCommand);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format meaningful tools for single agent display
|
||||
*/
|
||||
formatMeaningfulTool(toolUse) {
|
||||
const toolName = toolUse.name;
|
||||
const input = toolUse.input || {};
|
||||
|
||||
switch (toolName) {
|
||||
case 'Task':
|
||||
const description = input.description || 'analysis agent';
|
||||
return `🚀 Launching ${description}`;
|
||||
|
||||
case 'TodoWrite':
|
||||
if (this.showTodos) {
|
||||
return this.formatTodoUpdate(input);
|
||||
}
|
||||
return null;
|
||||
|
||||
case 'WebFetch':
|
||||
const domain = this.extractDomain(input.url || '');
|
||||
return `🌐 Fetching ${domain}`;
|
||||
|
||||
case 'Bash':
|
||||
// Only show meaningful bash commands
|
||||
const command = input.command || '';
|
||||
if (command.includes('nmap') || command.includes('subfinder') || command.includes('whatweb')) {
|
||||
const tool = command.split(' ')[0];
|
||||
return `🔍 Running ${tool}`;
|
||||
}
|
||||
return null;
|
||||
|
||||
// Browser tools (keep existing formatting)
|
||||
default:
|
||||
if (toolName.startsWith('mcp__playwright__browser_')) {
|
||||
return this.extractBrowserAction(toolUse);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format TodoWrite updates for display
|
||||
*/
|
||||
formatTodoUpdate(input) {
|
||||
if (!input.todos || !Array.isArray(input.todos)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const todos = input.todos;
|
||||
const inProgress = todos.filter(t => t.status === 'in_progress');
|
||||
const completed = todos.filter(t => t.status === 'completed');
|
||||
|
||||
if (completed.length > 0) {
|
||||
const recent = completed[completed.length - 1];
|
||||
return `✅ ${recent.content.slice(0, 50)}${recent.content.length > 50 ? '...' : ''}`;
|
||||
}
|
||||
|
||||
if (inProgress.length > 0) {
|
||||
const current = inProgress[0];
|
||||
return `🔄 ${current.content.slice(0, 50)}${current.content.length > 50 ? '...' : ''}`;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract meaningful status from turn data, suppressing internal operations
|
||||
*/
|
||||
extractMeaningfulStatus(turnData) {
|
||||
// Check for tool use first
|
||||
if (turnData.tool_use?.name) {
|
||||
// Suppress internal operations completely
|
||||
if (this.suppressedTools.has(turnData.tool_use.name)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Show browser testing actions
|
||||
if (turnData.tool_use.name.startsWith('mcp__playwright__browser_')) {
|
||||
return this.extractBrowserAction(turnData.tool_use);
|
||||
}
|
||||
|
||||
// Show Task agent launches
|
||||
if (turnData.tool_use.name === 'Task') {
|
||||
const description = turnData.tool_use.input?.description || 'analysis';
|
||||
return `🚀 ${description.slice(0, 40)}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse assistant text for progress milestones
|
||||
if (turnData.assistant_text) {
|
||||
return this.extractProgressFromText(turnData.assistant_text);
|
||||
}
|
||||
|
||||
return null; // Suppress everything else
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract browser action details
|
||||
*/
|
||||
extractBrowserAction(toolUse) {
|
||||
const actionType = toolUse.name.split('_').pop();
|
||||
|
||||
switch (actionType) {
|
||||
case 'navigate':
|
||||
const url = toolUse.input?.url || '';
|
||||
const domain = this.extractDomain(url);
|
||||
return `🌐 Testing ${domain}`;
|
||||
|
||||
case 'click':
|
||||
const element = toolUse.input?.element || 'element';
|
||||
return `🖱️ Clicking ${element.slice(0, 20)}`;
|
||||
|
||||
case 'fill':
|
||||
case 'form':
|
||||
return `📝 Testing form inputs`;
|
||||
|
||||
case 'snapshot':
|
||||
return `📸 Capturing page state`;
|
||||
|
||||
case 'type':
|
||||
return `⌨️ Testing input fields`;
|
||||
|
||||
default:
|
||||
return `🌐 Browser: ${actionType}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract meaningful progress from assistant text (single-agent mode only)
|
||||
*/
|
||||
extractProgressFromText(text) {
|
||||
// Only extract progress for single agents, not parallel ones
|
||||
if (this.mode !== 'single') {
|
||||
return null;
|
||||
}
|
||||
|
||||
// For single agents, be very conservative about what we show
|
||||
// Most progress should come from tool formatting, not text parsing
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract domain from URL for display
|
||||
*/
|
||||
extractDomain(url) {
|
||||
try {
|
||||
const urlObj = new URL(url);
|
||||
return urlObj.hostname || url.slice(0, 30);
|
||||
} catch {
|
||||
return url.slice(0, 30);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Redraw the status line showing all active agents
|
||||
*/
|
||||
redrawStatusLine() {
|
||||
// Clear previous line
|
||||
if (this.lastStatusLine) {
|
||||
process.stdout.write('\r' + ' '.repeat(this.lastStatusLine.length) + '\r');
|
||||
}
|
||||
|
||||
// Build new status line
|
||||
const statusEntries = Array.from(this.activeStatuses.entries())
|
||||
.map(([agent, status]) => `[${chalk.cyan(agent)}] ${status}`)
|
||||
.join(' | ');
|
||||
|
||||
if (statusEntries) {
|
||||
process.stdout.write(statusEntries);
|
||||
this.lastStatusLine = statusEntries.replace(/\u001b\[[0-9;]*m/g, ''); // Remove ANSI codes for length calc
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear status for a specific agent
|
||||
*/
|
||||
clearAgentStatus(agentName) {
|
||||
this.activeStatuses.delete(agentName);
|
||||
this.redrawStatusLine();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all statuses and finish the status line
|
||||
*/
|
||||
finishStatusLine() {
|
||||
if (this.lastStatusLine) {
|
||||
process.stdout.write('\n'); // Move to next line
|
||||
this.lastStatusLine = '';
|
||||
this.activeStatuses.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse JSON tool use from message content
|
||||
*/
|
||||
parseToolUse(content) {
|
||||
try {
|
||||
// Look for JSON tool use patterns
|
||||
const jsonMatch = content.match(/\{"type":"tool_use".*?\}/s);
|
||||
if (jsonMatch) {
|
||||
return JSON.parse(jsonMatch[0]);
|
||||
}
|
||||
} catch (error) {
|
||||
// Ignore parsing errors
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -76,7 +76,7 @@ const rollbackGitToCommit = async (targetRepo, commitHash) => {
|
||||
};
|
||||
|
||||
// Run a single agent with retry logic and checkpointing
|
||||
export const runSingleAgent = async (agentName, session, pipelineTestingMode, runClaudePromptWithRetry, loadPrompt, allowRerun = false, skipWorkspaceClean = false) => {
|
||||
const runSingleAgent = async (agentName, session, pipelineTestingMode, runClaudePromptWithRetry, loadPrompt, allowRerun = false, skipWorkspaceClean = false) => {
|
||||
// Validate agent first
|
||||
const agent = validateAgent(agentName);
|
||||
|
||||
@@ -299,7 +299,7 @@ export const runSingleAgent = async (agentName, session, pipelineTestingMode, ru
|
||||
};
|
||||
|
||||
// Run multiple agents in sequence
|
||||
export const runAgentRange = async (startAgent, endAgent, session, pipelineTestingMode, runClaudePromptWithRetry, loadPrompt) => {
|
||||
const runAgentRange = async (startAgent, endAgent, session, pipelineTestingMode, runClaudePromptWithRetry, loadPrompt) => {
|
||||
const agents = validateAgentRange(startAgent, endAgent);
|
||||
|
||||
console.log(chalk.cyan(`\n🔄 Running agent range: ${startAgent} to ${endAgent} (${agents.length} agents)`));
|
||||
@@ -323,7 +323,7 @@ export const runAgentRange = async (startAgent, endAgent, session, pipelineTesti
|
||||
};
|
||||
|
||||
// Run vulnerability agents in parallel
|
||||
export const runParallelVuln = async (session, pipelineTestingMode, runClaudePromptWithRetry, loadPrompt) => {
|
||||
const runParallelVuln = async (session, pipelineTestingMode, runClaudePromptWithRetry, loadPrompt) => {
|
||||
const vulnAgents = ['injection-vuln', 'xss-vuln', 'auth-vuln', 'ssrf-vuln', 'authz-vuln'];
|
||||
const activeAgents = vulnAgents.filter(agent => !session.completedAgents.includes(agent));
|
||||
|
||||
@@ -421,7 +421,7 @@ export const runParallelVuln = async (session, pipelineTestingMode, runClaudePro
|
||||
};
|
||||
|
||||
// Run exploitation agents in parallel
|
||||
export const runParallelExploit = async (session, pipelineTestingMode, runClaudePromptWithRetry, loadPrompt) => {
|
||||
const runParallelExploit = async (session, pipelineTestingMode, runClaudePromptWithRetry, loadPrompt) => {
|
||||
const exploitAgents = ['injection-exploit', 'xss-exploit', 'auth-exploit', 'ssrf-exploit', 'authz-exploit'];
|
||||
|
||||
// Get fresh session data to ensure we have the latest vulnerability analysis results
|
||||
|
||||
@@ -72,7 +72,7 @@ export const executeGitCommandWithRetry = async (commandArgs, sourceDir, descrip
|
||||
};
|
||||
|
||||
// Pure functions for Git workspace management
|
||||
export const cleanWorkspace = async (sourceDir, reason = 'clean start') => {
|
||||
const cleanWorkspace = async (sourceDir, reason = 'clean start') => {
|
||||
console.log(chalk.blue(` 🧹 Cleaning workspace for ${reason}`));
|
||||
try {
|
||||
// Check for uncommitted changes
|
||||
|
||||
Reference in New Issue
Block a user