#!/usr/bin/env bash
# ==============================================================
# kd — Kill Dev
# --------------------------------------------------------------
# Author : Kris Yotam (aka. khr1st)
# Contact : krisyotam@protonmail.com
# License : GNU GPLv3
# Date : 2026-03-25
# --------------------------------------------------------------
# Description:
# Kill dev servers and clean up stale lock files.
# Detects Next.js, Vite, Webpack, Hugo, and other common
# dev servers by process name and listening port.
# ==============================================================
set -eu
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BOLD='\033[1m'
RESET='\033[0m'
DEV_PORTS=(3000 3001 3080 3081 4000 4200 5000 5173 5174 8000 8080 8888)
DEV_PATTERNS=("next-server" "next dev" "vite" "webpack-dev" "hugo server" "gatsby" "nuxt" "remix" "astro" "turbopack")
LOCK_PATTERNS=(".next/dev/lock" ".next/server.lock")
usage() {
cat <<EOF
${BOLD}kd${RESET} — kill dev servers
${BOLD}Usage:${RESET}
kd Kill port 3000 + clean lock files (default)
kd --port <port> Kill a specific port
kd --all Find and kill all dev servers
kd --clean Remove stale lock files only
kd --scan Show running dev servers without killing
kd --help Show this help
${BOLD}Examples:${RESET}
kd Quick kill 3000
kd --port 5173 Kill Vite on 5173
kd --all Nuke everything
kd --scan See what's running
EOF
}
log() { printf "${GREEN}[kd]${RESET} %s\n" "$1"; }
warn() { printf "${YELLOW}[kd]${RESET} %s\n" "$1"; }
err() { printf "${RED}[kd]${RESET} %s\n" "$1"; }
kill_port() {
local port="$1"
local pids
pids=$(fuser "$port/tcp" 2>/dev/null | tr -s ' ') || true
if [ -z "$pids" ]; then
warn "Nothing on port $port"
return
fi
for pid in $pids; do
local name
name=$(ps -p "$pid" -o comm= 2>/dev/null || echo "unknown")
log "Killing $name (PID $pid) on port $port"
kill -9 "$pid" 2>/dev/null || true
done
}
clean_locks() {
local found=0
# search common dev directories
for dir in "$HOME"/dev/*/; do
[ -d "$dir" ] || continue
for pattern in "${LOCK_PATTERNS[@]}"; do
local lockfile="$dir$pattern"
if [ -f "$lockfile" ]; then
rm -f "$lockfile"
log "Removed $lockfile"
found=1
fi
done
done
if [ "$found" -eq 0 ]; then
warn "No stale lock files found"
fi
}
scan_devs() {
local found=0
printf "${BOLD}%-8s %-8s %-20s %s${RESET}\n" "PORT" "PID" "PROCESS" "CMD"
printf "%-8s %-8s %-20s %s\n" "----" "---" "-------" "---"
for port in "${DEV_PORTS[@]}"; do
local pids
pids=$(fuser "$port/tcp" 2>/dev/null | tr -s ' ') || true
[ -z "$pids" ] && continue
for pid in $pids; do
local name cmd
name=$(ps -p "$pid" -o comm= 2>/dev/null || echo "unknown")
cmd=$(ps -p "$pid" -o args= 2>/dev/null | head -c 60 || echo "")
printf "%-8s %-8s %-20s %s\n" "$port" "$pid" "$name" "$cmd"
found=1
done
done
# also check for dev processes not on standard ports
for pattern in "${DEV_PATTERNS[@]}"; do
while IFS= read -r line; do
local pid name
pid=$(echo "$line" | awk '{print $1}')
name=$(echo "$line" | awk '{for(i=2;i<=NF;i++) printf "%s ", $i; print ""}')
# skip if we already found this pid above
printf "%-8s %-8s %-20s %s\n" "?" "$pid" "${pattern}" "$name"
found=1
done < <(pgrep -af "$pattern" 2>/dev/null | grep -v "grep\|kd" || true)
done
if [ "$found" -eq 0 ]; then
warn "No dev servers detected"
fi
}
kill_all() {
log "Scanning for dev servers..."
# kill by known ports
for port in "${DEV_PORTS[@]}"; do
local pids
pids=$(fuser "$port/tcp" 2>/dev/null | tr -s ' ') || true
[ -z "$pids" ] && continue
kill_port "$port"
done
# kill by process pattern
for pattern in "${DEV_PATTERNS[@]}"; do
local pids
pids=$(pgrep -f "$pattern" 2>/dev/null | grep -v "$$" || true)
[ -z "$pids" ] && continue
for pid in $pids; do
local name
name=$(ps -p "$pid" -o comm= 2>/dev/null || echo "unknown")
log "Killing $name (PID $pid) matching '$pattern'"
kill -9 "$pid" 2>/dev/null || true
done
done
clean_locks
log "Done"
}
# --- main ---
case "${1:-}" in
--port|-p)
[ -z "${2:-}" ] && { err "Specify a port: kd --port 3000"; exit 1; }
kill_port "$2"
clean_locks
;;
--all|-a)
kill_all
;;
--clean|-c)
clean_locks
;;
--scan|-s)
scan_devs
;;
--help|-h)
usage
;;
"")
kill_port 3000
clean_locks
;;
*)
# if it's a number, treat as port
if [[ "$1" =~ ^[0-9]+$ ]]; then
kill_port "$1"
clean_locks
else
err "Unknown option: $1"
usage
exit 1
fi
;;
esac