Metadata-Version: 2.5
Name: portui
Version: 0.4.1
Summary: A terminal UI for monitoring and managing local listening ports
Project-URL: Repository, https://github.com/lowtrak/PorTUI
License: MIT
Requires-Python: >=3.9
Requires-Dist: psutil>=5.9.0
Requires-Dist: textual>=8.0.0
Description-Content-Type: text/markdown

# porTUI

A terminal UI application for monitoring local listening ports and managing the processes that own them.

## Features

- Real-time display of listening TCP/UDP ports with process information
- Configurable columns (Port, IP, Protocol, Process, PID, User, State, Command)
- Sortable by Port, IP, Protocol, Process, PID, User, State, or Command (numerical IP sorting)
- Sort order maintained during auto-refresh
- Inline tree view with real process hierarchy (toggle with `t`) — parent processes are resolved from the OS even when they don't hold ports (e.g. Chrome → Chrome Helper)
- Protocol filter to show TCP only, UDP only, or both (cycle with `h`)
- Real-time text filtering across all fields
- Port detail overlay with full untruncated command line and the process's working directory (press Enter, Escape to close)
- Auto-refresh with configurable interval (pauses during interaction)
- Prominent visual indicator when auto-refresh is paused
- Kill processes with choice of graceful (SIGTERM) or force (SIGKILL), with PID recycling guard
- Configuration persistence (`~/.config/portui/config.json`)
- Cross-platform (macOS, Linux, Windows)

## Installation & Usage

### Run without installing (recommended)

```bash
uvx portui
```

### Install globally

```bash
uv tool install portui
portui
```

### Install with pip

```bash
pip install portui
portui
```

### Run from source

```bash
git clone https://github.com/lowtrak/PorTUI
cd PorTUI
uv sync
uv run portui
```

### Run as a module

```bash
uv run python -m portui
```

## Remote monitoring (over SSH)

porTUI can monitor a remote host by running the existing `psutil`-based
collection over SSH — nothing is installed on the remote host beyond
`psutil` (which must already be present).

```bash
portui --host user@server
portui --host user@server -p 2222 -i ~/.ssh/id_ed25519 --remote-python /usr/bin/python3
```

- `--host user@server` — run in remote mode for the whole session (no
  localhost fallback). Per-invocation only; not persisted to config.
- `-p, --ssh-port` — SSH port (default 22).
- `-i, --identity` — SSH identity file (private key).
- `--remote-python` — Python interpreter on the remote (default `python3`).

The title bar shows the remote host (`porTUI v0.x.x — user@server`). The
status bar shows a latency indicator (round-trip time of the last
collection, e.g. `🟢 [123ms]`) and a connection status dot; when the SSH
connection is down it shows `🔴 [SSH: disconnected]` and the table stays
empty until the next refresh reconnects — the app does not crash.

All interactive features (tree view, filter, sort, column config, protocol
filter, kill, detail overlay) work unchanged against remote data, except that
the detail overlay's working directory is local-only and shows `— (remote)`. Killing a
remote process runs `kill` over SSH with the same PID-recycling guard as the
local flow.

**Requirements:** `ssh` on your local `$PATH` and `psutil` installed on the
remote host. No new runtime dependency is added (no `paramiko`).

## Keyboard Shortcuts

| Key | Action |
|-----|--------|
| `↑/↓` or `j/k` | Navigate rows |
| `Enter` | Show full port/process details |
| `/` | Focus filter input (Escape to clear and exit) |
| `h` | Cycle protocol filter (Both → TCP → UDP) |
| `c` | Toggle column configuration |
| `s` | Cycle sort column (Port → IP → Proto → Process → PID → User → State → Command); active sort shown in title bar |
| `t` | Toggle tree view (htop-style process hierarchy) |
| `x` | Kill selected process |
| `r` | Manual refresh |
| `p` | Pause/resume auto-refresh |
| `i` | Set refresh interval |
| `?` | Show help |
| `q` | Quit |

## Requirements

- Python 3.9+
- [uv](https://docs.astral.sh/uv/) (for `uvx` usage)

## Platform notes

### macOS

`psutil.net_connections()` requires root on macOS and will raise a permission error when called as a normal user. PorTUI works around this by iterating connections per-process instead, which only skips processes you don't have access to (typically kernel/system processes). No `sudo` required.

## Development

```bash
git clone https://github.com/lowtrak/PorTUI
cd PorTUI
uv sync --group dev
uv run pytest
```

### Project structure

```
portui/
├── __init__.py      # Public exports
├── __main__.py      # python -m portui entry point
├── app.py           # PortUIApp (main TUI application)
├── collector.py     # PortDataCollector (psutil, kill logic)
├── config.py        # ConfigManager (load/save/validate)
├── models.py        # PortInfo dataclass
├── screens.py       # All Screen subclasses
├── sort.py          # SortController
└── tree.py          # ProcessTreeBuilder (htop-style tree)

tests/
├── test_config.py   # ConfigManager validation (15 tests)
├── test_filter.py   # Filter mode UI (8 tests)
├── test_kill.py     # Kill + PID recycling guard (9 tests)
├── test_sort.py     # Sort all columns (21 tests)
└── test_tree.py     # Tree view (7 tests)
```
