/** * Built-in diagnostic skills — pre-crafted multi-step shell command bundles * targeting specific OS families. The Catty Agent calls `skill_run` with a * skillName and sessionId; this module expands that into the right commands * for the host's detected OS/shellType. * * Each skill is an ordered list of { label, shell, command } entries. The * executor runs them sequentially and assembles the outputs into a single * structured report the LLM can summarize for the user. */ export type SkillShellTarget = 'posix' | 'powershell' | 'cmd' | 'any'; export interface SkillStep { /** Short human label shown in the assembled report. */ label: string; /** Shell family this step is for. 'any' = run regardless. */ shell: SkillShellTarget; /** Command to send to the terminal. */ command: string; } export interface BuiltinSkill { id: string; /** Short description the model sees in the tool's docstring. */ description: string; /** If true, requires the session to be a remote SSH host. */ requiresRemoteHost?: boolean; /** Steps grouped by OS family. */ steps: SkillStep[]; } // --------------------------------------------------------------------------- // Skill registry — keep alphabetical by id // --------------------------------------------------------------------------- export const BUILTIN_SKILLS: Record = { // ---- diagnose_linux ----------------------------------------------------- diagnose_linux: { id: 'diagnose_linux', description: 'Quick Linux health check — CPU, memory, disk, load top processes, listening ports, Docker status, failed systemd services, recent kernel errors.', requiresRemoteHost: true, steps: [ { label: 'OS / kernel / uptime', shell: 'posix', command: 'uname -a; uptime; cat /etc/os-release 2>/dev/null | head -5' }, { label: 'CPU & memory', shell: 'posix', command: "echo '--- free -h ---'; free -h; echo '--- vmstat 1 2 ---'; vmstat 1 2 | tail -1" }, { label: 'Disk usage', shell: 'posix', command: "df -h 2>/dev/null; echo '--- inodes ---'; df -i 2>/dev/null | head -10" }, { label: 'Top processes by CPU', shell: 'posix', command: 'ps aux --sort=-%cpu 2>/dev/null | head -10 || ps aux | sort -k3 -rn | head -10' }, { label: 'Listening ports', shell: 'posix', command: "ss -tlnp 2>/dev/null | head -20 || netstat -tlnp 2>/dev/null | head -20" }, { label: 'Docker status (if present)', shell: 'posix', command: 'command -v docker >/dev/null 2>&1 && (docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" 2>&1 | head -15) || echo "docker not installed"' }, { label: 'Failed systemd services', shell: 'posix', command: "systemctl --failed --no-pager 2>/dev/null | head -20 || echo 'systemd not available'" }, { label: 'Recent kernel errors', shell: 'posix', command: "dmesg --level=err -n 2>/dev/null | tail -15 || journalctl -p err -n 15 --no-pager 2>/dev/null || echo 'no kernel error log available'" }, ], }, // ---- diagnose_windows --------------------------------------------------- diagnose_windows: { id: 'diagnose_windows', description: 'Quick Windows health check via PowerShell — OS, CPU, memory, disk, top processes, services, network adapters, recent errors.', requiresRemoteHost: true, steps: [ { label: 'OS & uptime', shell: 'powershell', command: '$os = Get-CimInstance Win32_OperatingSystem; "Computer: $($env:COMPUTERNAME)"; "OS: $($os.Caption) $($os.Version)"; "Uptime: $([math]::Round(((Get-Date) - $os.LastBootUpTime).TotalHours, 1)) hours"' }, { label: 'CPU', shell: 'powershell', command: 'Get-CimInstance Win32_Processor | Select-Object Name, NumberOfCores, LoadPercentage | Format-List' }, { label: 'Memory', shell: 'powershell', command: '$cs = Get-CimInstance Win32_ComputerSystem; $os = Get-CimInstance Win32_OperatingSystem; $totalGB = [math]::Round($cs.TotalPhysicalMemory/1GB, 1); $freeGB = [math]::Round($os.FreePhysicalMemory/1MB, 1); "Total: ${totalGB} GB, Free: ${freeGB} GB, Used: $([math]::Round(($totalGB - $freeGB)/$totalGB*100, 1))%"' }, { label: 'Disk', shell: 'powershell', command: 'Get-Volume -DriveLetter * | Where-Object DriveLetter | Select-Object DriveLetter, FileSystemLabel, FileSystem, @{N="TotalGB";E={[math]::Round($_.Size/1GB,1)}}, @{N="FreeGB";E={[math]::Round($_.SizeRemaining/1GB,1)}} | Format-Table -AutoSize' }, { label: 'Top processes (working set)', shell: 'powershell', command: 'Get-Process | Sort-Object WorkingSet64 -Descending | Select-Object -First 10 Name, Id, @{N="MB";E={[math]::Round($_.WorkingSet64/1MB,0)}}, CPU | Format-Table -AutoSize' }, { label: 'Services running', shell: 'powershell', command: 'Get-Service | Where-Object Status -eq Running | Measure-Object | Select-Object -ExpandProperty Count | ForEach-Object { "$_ services running" }; Get-Service | Where-Object {$_.Status -ne "Running" -and $_.StartType -ne "Disabled"} | Select-Object -First 15 Name, Status, StartType | Format-Table -AutoSize' }, { label: 'Listening ports', shell: 'powershell', command: 'Get-NetTCPConnection -State Listen -ErrorAction SilentlyContinue | Select-Object LocalAddress, LocalPort, OwningProcess | Sort-Object LocalPort | Format-Table -AutoSize' }, { label: 'Recent errors (System log)', shell: 'powershell', command: 'Get-WinEvent -LogName System -MaxEvents 50 -ErrorAction SilentlyContinue | Where-Object {$_.LevelDisplayName -eq "Error"} | Select-Object -First 10 TimeCreated, Id, ProviderName, Message | Format-List' }, ], }, // ---- check_ports -------------------------------------------------------- check_ports: { id: 'check_ports', description: 'Show all listening TCP/UDP ports with process info (ss/netstat on Linux, netstat/Get-NetTCPConnection on Windows).', steps: [ { label: 'Listening ports (POSIX)', shell: 'posix', command: "echo '=== ss ==='; ss -tulnp 2>/dev/null || echo 'ss not available'; echo '=== netstat ==='; netstat -tulnp 2>/dev/null | head -30 || true" }, { label: 'Listening ports (PowerShell)', shell: 'powershell', command: 'Write-Host "=== TCP Listen ===" ; Get-NetTCPConnection -State Listen -ErrorAction SilentlyContinue | Select-Object LocalAddress, LocalPort, OwningProcess | Sort-Object LocalPort | Format-Table -AutoSize; Write-Host "=== UDP ===" ; Get-NetUDPEndpoint -ErrorAction SilentlyContinue | Select-Object LocalAddress, LocalPort, OwningProcess | Sort-Object LocalPort | Format-Table -AutoSize' }, ], }, // ---- check_docker ------------------------------------------------------- check_docker: { id: 'check_docker', description: 'Check Docker daemon health, running containers, disk usage, and recent images.', requiresRemoteHost: true, steps: [ { label: 'Docker version / info', shell: 'any', command: 'docker version 2>&1 | head -15; echo "---"; docker info 2>&1 | head -20' }, { label: 'Containers (all)', shell: 'any', command: 'docker ps -a --format "table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}" 2>&1' }, { label: 'Docker disk usage', shell: 'any', command: 'docker system df 2>&1' }, { label: 'Top images', shell: 'any', command: 'docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}" 2>&1 | head -15' }, ], }, // ---- security_audit ----------------------------------------------------- security_audit: { id: 'security_audit', description: 'Basic Linux security posture check — SSH config, firewall, failed SSH logins, world-writable files, listening ports.', requiresRemoteHost: true, steps: [ { label: 'SSHD config quick check', shell: 'posix', command: "echo '=== PermitRootLogin ==='; grep -i 'PermitRootLogin' /etc/ssh/sshd_config 2>/dev/null || echo '(not set = default)'; echo '=== PasswordAuthentication ==='; grep -i 'PasswordAuthentication' /etc/ssh/sshd_config 2>/dev/null || echo '(not set = default)'" }, { label: 'Firewall status', shell: 'posix', command: "echo '=== ufw ==='; command -v ufw >/dev/null 2>&1 && ufw status 2>&1 || echo 'ufw not installed'; echo '=== firewalld ==='; command -v firewall-cmd >/dev/null 2>&1 && firewall-cmd --state 2>&1 || echo 'firewalld not installed'" }, { label: 'Failed SSH logins (last)', shell: 'posix', command: "echo '=== Recent failed SSH ==='; lastb 2>/dev/null | head -10 || echo 'lastb not available'" }, { label: 'World-writable files in /tmp (non-sticky)', shell: 'posix', command: 'find /tmp -maxdepth 2 -type f ! -sticky -perm -0002 2>/dev/null | head -10 || echo "ok"' }, { label: 'Listening ports', shell: 'posix', command: "ss -tlnp 2>/dev/null | head -20 || netstat -tlnp 2>/dev/null | head -20" }, ], }, }; /** All skill ids, exported so callers can validate. */ export const BUILTIN_SKILL_IDS = Object.keys(BUILTIN_SKILLS); /** Resolve a skill by id (case-insensitive). */ export function getBuiltinSkill(skillId: string): BuiltinSkill | null { if (!skillId) return null; return BUILTIN_SKILLS[skillId.toLowerCase()] ?? null; } /** Return steps matching the host's shell family (run 'any' on every host). */ export function filterStepsForShell(skill: BuiltinSkill, shellType?: string): SkillStep[] { const shell = (shellType || 'posix').toLowerCase(); return skill.steps.filter(step => { if (step.shell === 'any') return true; if (step.shell === shell) return true; // Treat 'fish' / unknown POSIX-ish shells as posix for diagnostics. if (step.shell === 'posix' && (shell === 'fish' || shell === 'unknown' || shell === 'posix')) return true; return false; }); }