import { formatCliCommand } from "../cli/command-format.js"; import type { PortListener, PortListenerKind, PortUsage } from "./ports-types.js"; export function classifyPortListener(listener: PortListener, port: number): PortListenerKind { const raw = `${listener.commandLine ?? ""} ${listener.command ?? ""}`.trim().toLowerCase(); if (raw.includes("clawdbot")) return "gateway"; if (raw.includes("ssh")) { const portToken = String(port); const tunnelPattern = new RegExp( `-(l|r)\\s*${portToken}\\b|-(l|r)${portToken}\\b|:${portToken}\\b`, ); if (!raw || tunnelPattern.test(raw)) return "ssh"; return "ssh"; } return "unknown"; } export function buildPortHints(listeners: PortListener[], port: number): string[] { if (listeners.length === 0) return []; const kinds = new Set(listeners.map((listener) => classifyPortListener(listener, port))); const hints: string[] = []; if (kinds.has("gateway")) { hints.push( `Gateway already running locally. Stop it (${formatCliCommand("clawdbot daemon stop")}) or use a different port.`, ); } if (kinds.has("ssh")) { hints.push( "SSH tunnel already bound to this port. Close the tunnel or use a different local port in -L.", ); } if (kinds.has("unknown")) { hints.push("Another process is listening on this port."); } if (listeners.length > 1) { hints.push( "Multiple listeners detected; ensure only one gateway/tunnel per port unless intentionally running isolated profiles.", ); } return hints; } export function formatPortListener(listener: PortListener): string { const pid = listener.pid ? `pid ${listener.pid}` : "pid ?"; const user = listener.user ? ` ${listener.user}` : ""; const command = listener.commandLine || listener.command || "unknown"; const address = listener.address ? ` (${listener.address})` : ""; return `${pid}${user}: ${command}${address}`; } export function formatPortDiagnostics(diagnostics: PortUsage): string[] { if (diagnostics.status !== "busy") { return [`Port ${diagnostics.port} is free.`]; } const lines = [`Port ${diagnostics.port} is already in use.`]; for (const listener of diagnostics.listeners) { lines.push(`- ${formatPortListener(listener)}`); } for (const hint of diagnostics.hints) { lines.push(`- ${hint}`); } return lines; }