pranshuparmar/witr
Why is this running? Trace any process, port, container, or file back to what started it - CLI + TUI. - pranshuparmar/witr
18.4k stars · 584 forks · 39 watchers
README
witr Why is this running? Trace any process, port, container, or file back to the exact chain that started it — one command, machine-readable JSON, or an interactive TUI.
🎮 Try witr in your browser → Investigate a simulated Linux box — a guided tutorial and free-play sandbox, no install required.
Purpose • Installation • TUI • Flags • Core Concept • Examples
Output Behavior • Platforms • Success Criteria • Sponsors
1. Purpose witr exists to answer a single question:
Why is this running?
When something is running on a system, whether it is a process, a service, or something bound to a port, there is always a cause. That cause is often indirect, non-obvious, or spread across multiple layers such as supervisors, containers, services, or shells. Existing tools (ps, top, lsof, ss, systemctl, docker ps) expose state and metadata. They show what is running, but leave the user to infer why by manually correlating outputs across tools. witr makes that causality explicit. It explains where a running thing came from, how it was started, and what chain of systems is responsible for it existing right now, in a single, human-readable output or an interactive TUI dashboard.
📖 Curious how witr came to be? Read the story or browse the Hacker News discussion.
2. Installation witr is distributed as a single static binary for Linux, macOS, FreeBSD, and Windows. witr is also independently packaged and maintained across multiple operating systems and ecosystems. An up-to-date overview of packaging status is available on Repology. Please note that community packages may lag GitHub releases due to independent review and validation. TipIf you use a package manager (Homebrew, Conda, Winget, etc.), we recommend installing via that for easier updates. Otherwise, the install script is the quickest way to get started.
2.1 Quick Install Unix (Linux, macOS & FreeBSD) curl -fsSL https://raw.githubusercontent.com/pranshuparmar/witr/main/install.sh | bash
Script Details The script will:
Detect your operating system (linux, darwin or freebsd) Detect your CPU architecture (amd64 or arm64) Download the latest released binary and man page Install it to /usr/local/bin/witr Install the man page to /usr/local/share/man/man1/witr.1 Pass INSTALL_PREFIX to override default install path
Windows (PowerShell) irm https://raw.githubusercontent.com/pranshuparmar/witr/main/install.ps1 | iex
Script Details The script will:
Download the latest release (zip) and verify checksum. Extract witr.exe to %LocalAppData%\witr\bin. Add the bin directory to your User PATH.
2.2 Package Managers
APT (Debian, Ubuntu & Derivatives)
You can install witr from the official Debian and Ubuntu repositories (Ubuntu 26.04+, Debian sid and later), as well as derivative distributions like Kali Linux, Devuan, and Raspbian: sudo apt install witr
Note: The apt-shipped version may lag the latest GitHub release. For the newest features, use the install script or another installation method.
Homebrew (macOS & Linux)
You can install witr using Homebrew on macOS or Linux: brew install witr
MacPorts (macOS)
You can install witr using MacPorts on macOS: sudo port install witr
Conda (macOS, Linux & Windows)
You can install witr using conda, mamba, or pixi on macOS, Linux, and Windows: conda install -c conda-forge witr
alternatively using mamba
mamba install -c conda-forge witralternatively using pixi
pixi global install witrArch Linux (AUR)
On Arch Linux and derivatives, install from the AUR package: yay -S witr-bin
alternatively using paru
paru -S witr-binor use your preferred AUR helper
Winget (Windows)
You can install witr via winget: winget install -e --id PranshuParmar.witr
NPM (Cross-platform)
You can install witr using npm: npm install -g @pranshuparmar/witr
FreeBSD Ports
You can install witr on FreeBSD from the FreshPorts port: pkg install witr
or
pkg install sysutils/witr Or build from Ports: cd /usr/ports/sysutils/witr/ make install cleanChocolatey (Windows)
You can install witr using Chocolatey: choco install witr
Scoop (Windows)
You can install witr using Scoop: scoop install main/witr
AOSC OS
You can install witr from the AOSC OS repository: oma install witr
GNU Guix
You can install witr from the GNU Guix repository: guix install witr
Uniget (Linux)
You can install witr using uniget: uniget install witr
Aqua (macOS, Linux & Windows)
You can install witr using aqua:
Add package
aqua g -i pranshuparmar/witrInstall package
aqua i pranshuparmar/witrBrioche (Linux)
You can install witr using brioche: brioche install -r witr
Mise (macOS, Linux & Windows)
You can install witr using mise: mise use github:pranshuparmar/witr
Prebuilt Packages (deb, rpm, apk)
witr provides native packages for major Linux distributions. You can download the latest .deb, .rpm, or .apk package from the GitHub releases page.
Generic download command using curl:
Replace <package name with the actual package that you need>
curl -LO https://github.com/pranshuparmar/witr/releases/latest/download/<package-name>Debian/Ubuntu (.deb): sudo dpkg -i ./witr-*.deb
Or, using apt for dependency resolution:
sudo apt install ./witr-*.debFedora/RHEL/CentOS (.rpm): sudo rpm -i ./witr-*.rpm
Alpine Linux (.apk): sudo apk add --allow-untrusted ./witr-*.apk
2.3 Source & Manual Installation
Go (cross-platform)
You can install the latest version directly from source: go install github.com/pranshuparmar/witr/cmd/witr@latest This will place the witr binary in your $GOPATH/bin or $HOME/go/bin directory. Make sure this directory is in your PATH.
Manual Installation
If you prefer manual installation, follow these simple steps for your platform: Unix (Linux, macOS, FreeBSD)
1. Determine OS and Architecture
OS=$(uname -s | tr '[:upper:]' '[:lower:]') ARCH=$(uname -m) [ "$ARCH" = "x86_64" ] && ARCH="amd64" [ "$ARCH" = "aarch64" ] && ARCH="arm64"2. Download the binary
curl -fsSL "https://github.com/pranshuparmar/witr/releases/latest/download/witr-${OS}-${ARCH}" -o witr3. Verify checksum (Optional)
curl -fsSL "https://github.com/pranshuparmar/witr/releases/latest/download/SHA256SUMS" -o SHA256SUMS grep "witr-${OS}-${ARCH}" SHA256SUMS | (sha256sum -c - 2>/dev/null || shasum -a 256 -c - 2>/dev/null) rm SHA256SUMS4. Rename and install
chmod +x witr sudo mkdir -p /usr/local/bin sudo mv witr /usr/local/bin/witr5. Install man page (Optional)
sudo mkdir -p /usr/local/share/man/man1 sudo curl -fsSL https://github.com/pranshuparmar/witr/releases/latest/download/witr.1 -o /usr/local/share/man/man1/witr.1 Windows (PowerShell)1. Determine Architecture
if ($env:PROCESSOR_ARCHITECTURE -eq "AMD64") { $ZipName = "witr-windows-amd64.zip" } elseif ($env:PROCESSOR_ARCHITECTURE -eq "ARM64") { $ZipName = "witr-windows-arm64.zip" } else { Write-Error "Unsupported architecture: $($env:PROCESSOR_ARCHITECTURE)" exit 1 }2. Download the zip
Invoke-WebRequest -Uri "https://github.com/pranshuparmar/witr/releases/latest/download/$ZipName" -OutFile "witr.zip"3. Extract the binary
Expand-Archive -Path "witr.zip" -DestinationPath "." -Force4. Verify checksum (Optional)
Invoke-WebRequest -Uri "https://github.com/pranshuparmar/witr/releases/latest/download/SHA256SUMS" -OutFile "SHA256SUMS" $hash = Get-FileHash -Algorithm SHA256 .\witr.zip $expected = Select-String -Path .\SHA256SUMS -Pattern $ZipName if ($expected -and $hash.Hash.ToLower() -eq $expected.Line.Split(' ')[0]) { Write-Host "Checksum OK" } else { Write-Host "Checksum Mismatch" }5. Install to local bin directory
$InstallDir = "$env:LocalAppData\witr\bin" New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null Move-Item .\witr.exe $InstallDir\witr.exe -Force6. Add to User Path (Persistent)
$UserPath = [Environment]::GetEnvironmentVariable("Path", "User") if ($UserPath -notlike "$InstallDir") { [Environment]::SetEnvironmentVariable("Path", "$UserPath;$InstallDir", "User") $env:Path += ";$InstallDir" Write-Host "Added to Path. You may need to restart PowerShell." }7. Cleanup
Remove-Item witr.zip Remove-Item SHA256SUMS2.4 Run Without Installation
Nix Flake
If you use Nix, you can build witr from source and run without installation: nix run github:pranshuparmar/witr -- --help
Pixi
If you use pixi, you can run without installation on Linux or macOS: pixi exec witr --help
2.5 Other Operations
Verify Installation
witr --version man witr
Shell Completions
witr supports tab completion for all flags. To enable it, add the appropriate line to your shell configuration: Bash echo 'eval "$(witr completion bash)"' >> ~/.bashrc source ~/.bashrc Zsh echo 'eval "$(witr completion zsh)"' >> ~/.zshrc source ~/.zshrc Fish witr completion fish | source
To make it permanent:
witr completion fish > ~/.config/fish/completions/witr.fish PowerShell witr completion powershell | Out-String | Invoke-ExpressionTo make it permanent, add the above line to your $PROFILE
Uninstallation
If you installed via a package manager (Homebrew, Conda, etc.), please use the respective uninstall command (e.g., brew uninstall witr). To completely remove script/manual installation of witr: Unix (Linux, macOS, FreeBSD) sudo rm -f /usr/local/bin/witr sudo rm -f /usr/local/share/man/man1/witr.1 Windows Remove-Item -Recurse -Force "$env:LocalAppData\witr"
3. Interactive Mode (TUI) Running witr without any arguments or with the -i flag launches the Interactive Mode (TUI). This provides a real-time, terminal-based dashboard with four tabs for exploring processes, ports, containers, and file locks. Key Features:
Processes Tab: Live, sortable, filterable list of all running processes with a side panel showing the ancestry tree of the highlighted process. Ports Tab: Open/listening ports with the owning processes attached in a side panel. Toggle between LISTEN-only and ALL with a. Containers Tab: All running containers across Docker, Podman, nerdctl, K8s/crictl, Incus, LXC, LXD, and FreeBSD jails in one list - name, image, status, ports, command, plus a per-container detail view with mounts, networks, and compose project metadata. Locks Tab: System-wide file locks (POSIX/FLOCK on Linux, lsof-derived on macOS/FreeBSD). Press a to switch into "all open files" mode, where locked entries are merged with every interesting open fd; type into / to search across the merged set. Process Details: Deep-dive into a process to see its full ancestry tree, child processes, environment variables, working directory, sockets, file context, and more. Process Actions: Send signals (Kill, Terminate, Pause, Resume) or Renice processes directly from the UI (Unix only). Mouse Support: Navigate, sort columns, and click rows using your mouse. Adaptive Theme: Colors adapt automatically to light and dark terminal backgrounds. Auto-Refresh: The process, port, container, and lock lists refresh automatically on an adaptive cadence (starts at 3 seconds, backing off under load).
4. Flags & Options -c, --container strings container(s) to look up (repeatable) --env show environment variables for the process -x, --exact use exact name matching (no substring search) -f, --file strings file(s) held open by a process (repeatable) -h, --help help for witr -i, --interactive interactive mode (TUI) --json show result as JSON --no-color disable colorized output -p, --pid strings pid(s) to look up (repeatable) -o, --port strings port(s) to look up (repeatable) -s, --short show only ancestry -t, --tree show only ancestry as a tree --verbose show extended process information -v, --version version for witr --warnings show only warnings
Positional arguments (without flags) are treated as process or service names. Multiple names can be passed. By default, name matching uses substring matching (fuzzy search). Use --exact to match only processes with the exact name. All target flags (--pid, --port, --file, --container) are repeatable and can be mixed with each other and with positional name arguments. When multiple targets are provided, results are shown sequentially with labeled dividers. All output modes (standard, short, tree, JSON, env, warnings, verbose) work with multiple inputs. The --container flag searches across Docker, Podman, nerdctl, K8s/crictl, Incus, LXC, LXD, and FreeBSD jails, and matches against container name, image, command, and compose project/service labels. The TUI is launched if no arguments or relevant flags (--pid, --port, --file, --container) are provided, or if the --interactive flag is explicitly used.
5. Core Concept witr treats everything as a process question. Ports, services, containers, and commands all eventually map to PIDs. Once a PID is identified, witr builds a causal chain explaining why that PID exists. At its core, witr answers:
What is running? How did it start? What is keeping it running? What context does it belong to?
6. Example Outputs
💡 Prefer learning by doing? The interactive browser tutorial walks you through outputs like these live on a simulated box — for a better feel of witr, no install required.
6.1 Name Based Query witr node Target : node
Process : node (pid 14233) User : pm2 Command : node index.js Started : 2 days ago (Mon 2025-02-02 11:42:10 +05:30)
Why It Exists : systemd (pid 1) → pm2 (pid 5034) → node (pid 14233)
Source : pm2
Working Dir : /opt/apps/expense-manager Git Repo : expense-manager (main) Sockets : 127.0.0.1:5001 (TCP | LISTENING)
6.2 Short Output witr --port 5000 --short systemd (pid 1) → PM2 v5.3.1: God (pid 1481580) → python (pid 1482060)
6.3 Tree Output witr --pid 143895 --tree systemd (pid 1) └─ init-systemd(Ub (pid 2) └─ SessionLeader (pid 143858) └─ Relay(143860) (pid 143859) └─ bash (pid 143860) └─ sh (pid 143886) └─ node (pid 143895) ├─ node (pid 143930) ├─ node (pid 144189) └─ node (pid 144234)
Note: Tree view includes child processes (up to 10) and highlights the target process.
6.4 Multiple Matches witr ng Multiple matching processes found:
[1] nginx (pid 2311) nginx -g daemon off; [2] nginx (pid 24891) nginx -g daemon off; [3] ngrok (pid 14233) ngrok http 5000
Re-run with: witr --pid <pid>
To avoid substring matching and only find processes with an exact name, use the --exact flag: witr nginx -x
6.5 File Based Query witr --file /var/lib/dpkg/lock Explains the process holding a file open.
6.6 Container Based Query witr --container redis Looks up a container by name, image, command, or compose project/service across every detected runtime (Docker, Podman, nerdctl, K8s/crictl, Incus, LXC, LXD, FreeBSD jails). Pass --verbose to include mounts, networks, and compose metadata in the output.
6.7 Multiple Inputs witr nginx --port 5432 --pid 1234 ----- [name: nginx] ----- Target : nginx Process : nginx (pid 2311) ...
----- [port: 5432] ----- Target : postgres Process : postgres (pid 891) ...
----- [pid: 1234] ----- Target : node Process : node (pid 1234) ...
All target flags are repeatable and can be mixed. Results appear in the order you typed them. All output modes (--short, --tree, --json, --env, --warnings, --verbose) work with multiple inputs.
7. Output Behavior 7.1 Output Principles
Single screen by default (best effort) Deterministic ordering Narrative-style explanation Best-effort detection with explicit uncertainty
7.2 Exit Codes witr returns meaningful exit codes for use in scripts, CI pipelines, and monitoring:
Code Meaning
0 Clean: process found, no warnings
1 Warnings: process found but has one or more warnings
2 Not found: no matching process or service
3 Permission denied: insufficient privileges
4 Invalid input: bad arguments or ambiguous match
5 Internal error: an unexpected failure occurred
Example Usage: witr nginx --short case $? in 0) echo "All clear" ;; 1) echo "Warnings detected" ;; 2) echo "Process not running" ;; 3) echo "Need elevated privileges" ;; 4) echo "Invalid input or ambiguous match" ;; 5) echo "Internal error" ;; esac
7.3 Standard Output Sections Target What the user asked about. Process Executable, PID, user, command, start time and restart count. Why It Exists A causal ancestry chain showing how the process came to exist. This is the core value of witr. Source The primary system responsible for starting or supervising the process (best effort). Examples:
systemd unit with schedule info for timer-triggered services (Linux) launchd service with schedule/trigger details (macOS) SSH session (with remote IP and terminal) docker container pm2 cron interactive shell (detects tmux/screen sessions) Snap/Flatpak sandbox (Linux)
Only one primary source is selected. Context (best effort)
Working directory Git repository name and branch Container name / image (docker, podman, kubernetes, colima, containerd) Public vs private bind
Warnings Non‑blocking observations such as:
Process is running as root Dangerous Linux capabilities on non-root processes (CAP_SYS_ADMIN, etc.) Process is listening on a public interface (0.0.0.0 / ::) Restarted multiple times (warning only if above threshold) Process is using high memory (>1GB RSS) Process has been running for over 90 days Deleted binary, library injection indicators (LD_PRELOAD, DYLD_*)
8. Platform Support
Linux (x86_64, arm64) - Full feature support (/proc). macOS (x86_64, arm64) - Uses ps, lsof, sysctl, pgrep. Windows (x86_64, arm64) - Native Win32 APIs (ToolHelp32, PSAPI, Service Control Manager). No PowerShell or WMI dependency. FreeBSD (x86_64, arm64) - Uses procstat, ps, lsof.
8.1 Feature Compatibility Matrix
Feature Linux macOS Windows FreeBSD Notes
Process Selection
By Name ✅ ✅ ✅ ✅
By PID ✅ ✅ ✅ ✅
By Port ✅ ✅ ✅ ✅
By File ✅ ✅ ✅ ✅
By Container ✅ ✅ ✅ ✅ Requires the runtime CLI on PATH (docker/podman/nerdctl/crictl/incus/lxc/lxc-ls/jls).
Multiple/mixed inputs ✅ ✅ ✅ ✅ Repeatable flags, mixed types.
Exact Match ✅ ✅ ✅ ✅
Full command line ✅ ✅ ✅ ✅
Process start time ✅ ✅ ✅ ✅
Working directory ✅ ✅ ✅ ✅
Environment variables ✅ ⚠️ ⚠️ ✅ macOS: SIP restrictions; Windows: protected processes inaccessible.
Network
Listening ports ✅ ✅ ✅ ✅
Bind addresses ✅ ✅ ✅ ✅
Port → PID resolution ✅ ✅ ✅ ✅
Port → Container fallback ✅ ✅ ✅ ✅ Used when the port is owned by PID 1 via systemd socket activation or a container runtime.
Service Detection
Service Manager ✅ ✅ ✅ ✅ Linux: systemd, macOS: launchd, Windows: Services, FreeBSD: rc.d
Service Description ✅ ✅ ✅ ✅ Linux: Description, macOS: Comment, Windows: Display Name, FreeBSD: rc header
Configuration Source ✅ ✅ ✅ ✅ Linux: Unit File, macOS: Plist, Windows: Registry Key, FreeBSD: Rc Script
Supervisor ✅ ✅ ✅ ✅
Containers ✅ ✅ ✅ ✅ Docker (plus compose mappings), Podman, nerdctl, K8s (Kubepods/crictl), Containerd. Colima on macOS/Linux. Incus/LXC/LXD on Linux. Jails on FreeBSD.
SSH session detection ✅ ✅ ✅ ✅ Detects remote IP and terminal.
tmux/screen detection ✅ ✅ ❌ ✅ Shows session name in source.
Schedule detection ✅ ✅ ❌ ❌ Linux: systemd timers, macOS: launchd intervals/calendar.
Snap/Flatpak detection ✅ ❌ ❌ ❌
Health & Diagnostics
CPU usage detection ✅ ✅ ✅ ✅
Memory usage detection ✅ ✅ ✅ ✅
Health status detection ✅ ✅ ✅ ✅
Open Files / Handles ✅ ✅ ⚠️ ✅ Windows: count only.
File Locks ✅ ✅ ❌ ✅ Linux: /proc/locks; macOS/FreeBSD: derived from lsof/fstat.
Deleted binary detection ✅ ✅ ✅ ✅ Warns if executable is missing.
Capability warnings ✅ ❌ ❌ ❌ Warns about dangerous capabilities on non-root processes.
Context
Git repo/branch detection ✅ ✅ ✅ ✅
Interactive Mode (TUI)
Processes Tab ✅ ✅ ✅ ✅
Ports Tab ✅
