import http from "http"; import { existsSync, readFileSync, readdirSync } from "fs"; import { resolve } from "path"; import { homedir } from "os"; import { hackerTools } from "../core/hacker-tools.js"; import { readFile, writeFile } from "fs/promises"; // WebSocket imported dynamically to avoid build issues on USB let WebSocketServer: any; let WebSocket: any; try { const ws = require("ws"); WebSocketServer = ws.WebSocketServer; WebSocket = ws.WebSocket; } catch {} const REPORTS_DIR = resolve(homedir(), ".config", "phantom", "reports"); const PLAYBOOKS_DIR = resolve(homedir(), ".config", "phantom", "playbooks"); const SESSIONS_DIR = resolve(homedir(), ".config", "phantom", "sessions"); const MISSIONS_DIR = resolve(homedir(), ".config", "phantom", "missions"); // WebSocket clients for real-time updates const wsClients = new Set(); function broadcast(data: any) { const msg = JSON.stringify(data); for (const client of wsClients) { if (client.readyState === WebSocket.OPEN) { client.send(msg); } } } // Broadcast helpers for specific events function broadcastHealth() { const mem = process.memoryUsage(); const uptime = process.uptime(); broadcast({ type: "health", data: { status: "ok", uptime: Math.floor(uptime), uptimeHuman: formatUptime(uptime), memory: { rss: Math.round(mem.rss / 1024 / 1024) + "MB", heapUsed: Math.round(mem.heapUsed / 1024 / 1024) + "MB", heapTotal: Math.round(mem.heapTotal / 1024 / 1024) + "MB", external: Math.round(mem.external / 1024 / 1024) + "MB" }, pid: process.pid, platform: process.platform, nodeVersion: process.version, usbMounted: existsSync("/root/usb/Phantom"), timestamp: new Date().toISOString() } }); } function broadcastMissions() { const missions: any[] = []; if (existsSync(MISSIONS_DIR)) { for (const f of readdirSync(MISSIONS_DIR).filter((f: string) => f.endsWith(".json"))) { const m = JSON.parse(readFileSync(resolve(MISSIONS_DIR, f), "utf-8")); missions.push({ id: m.id, programName: m.programName, programHandle: m.programHandle, status: m.status, createdAt: m.createdAt, updatedAt: m.updatedAt, inScopeCount: m.scope?.inScope?.length || 0, objectives: m.objectives?.length || 0 }); } } missions.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()); broadcast({ type: "missions", data: missions }); } function broadcastSchedules() { const schedules = (globalThis as any).__phantomSchedules || []; broadcast({ type: "schedules", data: schedules.map((s: any) => ({ id: s.id, tool: s.tool, target: s.target, interval: s.interval, nextAt: s.nextAt, nextAtHuman: new Date(s.nextAt).toLocaleString() })) }); } const HTML = ` Phantom Dashboard

🔮 PHANTOM

online
🛠 Tools
📋 Playbooks
📄 Reports
📊 Monitor
Loading tools...
Loading playbooks...
Loading reports...

🖥 System Health

Status OK
Uptime—
Memory—
CPU Load—
Disk (USB)—
Node—
PID—
USB Mount✓ Mounted
Platform—
Version—
Network—
Disk I/O—
Uptime (raw)—
Platform—
Version—
PID—

🎯 Active Missions

Loading missions...

⏰ Active Schedules

Loading schedules...

📋 All Tasks (Searchable)

Loading tasks...
—● connected
`; export function startGui(port: number = parseInt(process.env.PHANTOM_PORT || "8080")): void { const server = http.createServer(async (req, res) => { res.setHeader("Access-Control-Allow-Origin", "*"); res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); res.setHeader("Access-Control-Allow-Headers", "Content-Type"); if (req.method === "OPTIONS") { res.writeHead(204); res.end(); return; } const url = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`); const path = url.pathname; try { if (path === "/api/tools") { res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify(Object.keys(hackerTools).sort())); return; } if (path === "/api/run" && req.method === "POST") { const body = await readBody(req); const { tool, args } = JSON.parse(body); const fn = hackerTools[tool]; if (!fn) { res.writeHead(404); res.end(JSON.stringify({ error: "Tool not found" })); return; } const result = await fn.execute(args); res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify({ result })); return; } if (path === "/api/playbooks") { const names: any[] = []; if (existsSync(PLAYBOOKS_DIR)) { for (const f of readdirSync(PLAYBOOKS_DIR).filter((f: string) => f.endsWith(".json"))) { const pb = JSON.parse(readFileSync(resolve(PLAYBOOKS_DIR, f), "utf-8")); names.push({ name: pb.name, description: pb.description, steps: pb.steps?.length || 0, vars: pb.variables }); } } res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify(names)); return; } if (path === "/api/playbook/run" && req.method === "POST") { const body = await readBody(req); const { name, vars } = JSON.parse(body); const fn = hackerTools["playbook_run"]; if (!fn) { res.writeHead(404); res.end(JSON.stringify({ error: "playbook_run tool not found" })); return; } const input = vars ? `${name}|${vars}` : name; const result = await fn.execute(input); res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify({ result })); return; } if (path === "/api/reports") { const reports: any[] = []; if (existsSync(REPORTS_DIR)) { for (const f of readdirSync(REPORTS_DIR).filter((f: string) => f.endsWith(".md") || f.endsWith(".txt"))) { const stat = existsSync(resolve(REPORTS_DIR, f)) ? "" : ""; reports.push({ name: f, size: formatSize(readFileSync(resolve(REPORTS_DIR, f)).length) }); } } res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify(reports.sort((a, b) => a.name.localeCompare(b.name)))); return; } if (path.startsWith("/api/report/")) { const name = decodeURIComponent(path.slice(12)); const fp = resolve(REPORTS_DIR, name); if (!existsSync(fp)) { res.writeHead(404); res.end(JSON.stringify({ error: "Not found" })); return; } const content = readFileSync(fp, "utf-8"); res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify({ content })); return; } // ── Monitoring API endpoints ── if (path === "/api/missions") { const missions: any[] = []; if (existsSync(MISSIONS_DIR)) { for (const f of readdirSync(MISSIONS_DIR).filter((f: string) => f.endsWith(".json"))) { const m = JSON.parse(readFileSync(resolve(MISSIONS_DIR, f), "utf-8")); missions.push({ id: m.id, programName: m.programName, programHandle: m.programHandle, status: m.status, createdAt: m.createdAt, updatedAt: m.updatedAt, inScopeCount: m.scope?.inScope?.length || 0, objectives: m.objectives?.length || 0 }); } } res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify(missions.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()))); return; } if (path === "/api/schedules") { // Access globalThis.__phantomSchedules from the main process const schedules = (globalThis as any).__phantomSchedules || []; res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify(schedules.map((s: any) => ({ id: s.id, tool: s.tool, target: s.target, interval: s.interval, nextAt: s.nextAt, nextAtHuman: new Date(s.nextAt).toLocaleString() })))); return; } if (path === "/api/tasks") { // Get all available tasks from hackerTools const tasks = Object.keys(hackerTools).map(name => ({ name, category: categorizeTool(name) })); res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify(tasks)); return; } if (path === "/api/health") { const mem = process.memoryUsage(); const uptime = process.uptime(); res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify({ status: "ok", uptime: Math.floor(uptime), uptimeHuman: formatUptime(uptime), memory: { rss: Math.round(mem.rss / 1024 / 1024) + "MB", heapUsed: Math.round(mem.heapUsed / 1024 / 1024) + "MB", heapTotal: Math.round(mem.heapTotal / 1024 / 1024) + "MB", external: Math.round(mem.external / 1024 / 1024) + "MB" }, pid: process.pid, platform: process.platform, nodeVersion: process.version, usbMounted: existsSync("/root/usb/Phantom"), timestamp: new Date().toISOString() })); return; } if (path === "/api/system") { const { execFileSync } = await import("child_process"); let diskInfo = ""; let loadAvg = ""; try { diskInfo = execFileSync("df", ["-h", "/root/usb"], { encoding: "utf-8", timeout: 5000 }).trim(); } catch {} try { loadAvg = execFileSync("cat", ["/proc/loadavg"], { encoding: "utf-8", timeout: 5000 }).trim(); } catch {} res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify({ disk: diskInfo, loadAvg: loadAvg, cpus: require("os").cpus().length, totalMem: Math.round(require("os").totalmem() / 1024 / 1024 / 1024 * 10) / 10 + "GB", freeMem: Math.round(require("os").freemem() / 1024 / 1024 / 1024 * 10) / 10 + "GB" })); return; } // Serve HTML for everything else res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" }); res.end(HTML); } catch (e: any) { res.writeHead(500, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: e.message })); } }); // WebSocket server for real-time updates if (WebSocketServer) { const wss = new WebSocketServer({ server }); wss.on("connection", (ws: any) => { wsClients.add(ws); ws.on("close", () => wsClients.delete(ws)); ws.on("error", () => wsClients.delete(ws)); // Send initial state ws.send(JSON.stringify({ type: "welcome", timestamp: new Date().toISOString() })); }); // Periodic broadcasts for real-time updates setInterval(broadcastHealth, 5000); setInterval(broadcastMissions, 30000); setInterval(broadcastSchedules, 10000); } server.listen(port, () => { console.log(`\n 🌐 Phantom Dashboard: http://localhost:${port}\n`); }); } function readBody(req: http.IncomingMessage): Promise { return new Promise((resolve, reject) => { let b = ""; req.on("data", (c: string) => b += c); req.on("end", () => resolve(b)); req.on("error", reject); }); } function formatSize(bytes: number): string { if (bytes < 1024) return bytes + "B"; if (bytes < 1048576) return (bytes / 1024).toFixed(1) + "KB"; return (bytes / 1048576).toFixed(1) + "MB"; } function categorizeTool(name: string): string { if (name.startsWith("mission") || name.startsWith("hackerone")) return "mission"; if (name.startsWith("recon") || name === "subfinder" || name === "dnsx" || name === "httpx" || name === "sub_enum" || name === "wayback" || name === "amass") return "recon"; if (name.startsWith("net:") || name === "port_scan" || name === "ssl_check" || name === "whois" || name === "geoip" || name === "dns_lookup" || name === "reverse_dns" || name === "nmap" || name === "naabu") return "network"; if (name.startsWith("vuln:") || name === "cve_search" || name === "exploit_search" || name === "nuclei" || name === "nikto" || name === "sqlmap" || name === "sql_detect" || name === "xss_scan") return "vulnerability"; if (name.startsWith("web:") || name === "crawl" || name === "http_headers" || name === "web_fetch" || name === "web_links" || name === "web_snapshot" || name === "whatweb" || name === "wafw00f" || name === "robots_txt" || name === "dir_bruteforce" || name === "ffuf" || name === "gobuster") return "web"; if (name.startsWith("osint:") || name === "email_verify" || name === "email_breach" || name === "github_dork" || name === "shodan_search" || name === "vt_check" || name === "cert_expiry") return "osint"; if (name.startsWith("schedule") || name === "cron" || name === "batch" || name === "pipe" || name === "session" || name === "playbook") return "automation"; return "other"; } function formatUptime(seconds: number): string { const d = Math.floor(seconds / 86400); const h = Math.floor((seconds % 86400) / 3600); const m = Math.floor((seconds % 3600) / 60); const s = Math.floor(seconds % 60); const parts = []; if (d) parts.push(`${d}d`); if (h) parts.push(`${h}h`); if (m) parts.push(`${m}m`); if (s || parts.length === 0) parts.push(`${s}s`); return parts.join(" "); }