Know what's using your localhost.
PortSpy is a fast, Windows-first terminal UI for discovering local listening ports, understanding what is behind them, identifying where processes came from, and stopping processes you no longer need.
Instead of this:
TCP 127.0.0.1:3000 LISTENING 18432
PortSpy aims to show you this:
:3000 Next.js Codex 612 MB 12m
:3001 Next.js Claude Code 584 MB 8m
:5173 Vite Cursor 321 MB 1h
:5432 PostgreSQL Docker 148 MB 4h
No hunting through Task Manager.
No netstat + PID lookup + process-tree archaeology.
No wondering which AI coding agent started yet another dev server.
Note
The full v0.1 feature set is implemented: 93 tests green (core + TUI), cargo clippy --all-targets clean. The remaining release gates are validation on a real Windows machine (listener discovery, PID resolution, tool detection) and packaging — tracked in docs/devplan.md.
v0.1 does one thing and does it well:
discover local TCP listeners → identify their process and origin → inspect them → kill them from a clean TUI.
Windows 11 is the primary target for the first release.
Modern development environments create a surprising amount of background state.
A single project can involve:
- Codex
- Claude Code
- Cursor
- VS Code
- Windows Terminal
- PowerShell
- WSL2
- Docker
- Next.js
- Vite
- local databases
- development servers
- workers
- debuggers
And every one of them can start processes.
Eventually you end up asking:
What is using port 3000?
Why is there something on 3001?
Did Codex start this?
Is this Next.js instance still useful?
Can I safely kill it?
Windows can already give you most of the raw information.
The problem is that the information is fragmented.
PortSpy turns it into something useful.
PortSpy does not treat a listening socket as just:
PID 18432
It reconstructs the context around it.
For example:
WindowsTerminal.exe
└── pwsh.exe
└── codex.exe
└── node.exe
└── localhost:3000
PortSpy can then present that as:
Port :3000
Application Next.js
Origin Codex
Runtime Node.js
PID 18432
Memory 612 MB
Uptime 12m
The raw process information remains available when you want it.
The useful answer comes first.
The interface is intentionally minimal.
portspy BROWSE 8 listening · 12 total 2.1 GB · 12m
PORT APP ORIGIN PID PROCESS MEM UPTIME
:3000 Next.js Codex 18432 node.exe 612 MB 12m
:3001 Next.js Claude Code 20144 node.exe 584 MB 8m
:5173 Vite Cursor 5120 node.exe 321 MB 1h
:5432 PostgreSQL Docker 9812 postgres.exe 148 MB 4h
:8000 Python PowerShell 9021 python.exe 96 MB 5m
↑/↓ move · j/k vim · Enter inspect · / search · s sort · S order · x kill · q quit
Inspecting an entry opens the same list on the left and a details split on the right:
PORT APP ORIGIN PID │ Next.js
:3000 Next.js Codex 18432 │
:3001 Next.js Claude Code 20144 │ listener :3000 (tcp)
:5173 Vite Cursor 5120 │ address 127.0.0.1:3000
:5432 PostgreSQL Docker 9812 │ process node.exe
:8000 Python PowerShell 9021 │ pid 18432
│ memory 612 MB
│ uptime 12m
│ command pnpm dev
│ cwd C:\dev\my-app
│
│ ├── node.exe (pid 18432)
│ ├── codex.exe (pid 15320)
│ └── pwsh.exe (pid 9124)
The interface is designed around:
- low visual noise
- fast keyboard navigation
- stable selection during refreshes
- responsive terminal layouts
- useful information before implementation details
- no unnecessary dashboards
- no rainbow CLI
- no Matrix aesthetic
PortSpy discovers local listening TCP sockets and associates them with their owning processes.
The first release targets:
- IPv4
- IPv6
- loopback listeners
- all-interface listeners
- interface-specific listeners
PortSpy distinguishes between addresses such as:
127.0.0.1
::1
0.0.0.0
::
so that localhost-only listeners are not presented the same way as processes exposed on every interface.
For every listener, PortSpy attempts to retrieve:
- PID
- process name
- executable path
- command line
- parent PID
- process creation time
- memory usage
- CPU usage
- process ancestry
- working directory when available
Not every piece of metadata is always accessible on Windows.
Missing information is treated as missing information, not as a fatal error.
PortSpy reconstructs the parent process chain.
Example:
Windows Terminal
└── PowerShell
└── Claude Code
└── Node.js
This is what allows PortSpy to answer a much more useful question than:
Which executable owns this socket?
It can attempt to answer:
What actually caused this process to exist?
The first release is designed to recognize origins such as:
- Codex
- Claude Code
- Cursor
- VS Code
- Windows Terminal
- PowerShell
- CMD
- WSL2
- Docker
PortSpy keeps factual process information separate from inferred information.
For example:
Fact:
node.exe owns PID 18432
Inference:
the application appears to be Next.js
Inference:
the process appears to originate from Codex
Detection rules carry confidence information internally rather than pretending every inference is certain.
PortSpy tries to show the application you care about instead of only the runtime executable.
Instead of:
node.exe
it can identify:
Next.js
or:
Vite
Initial detection targets include:
- Next.js
- Vite
- Node.js
- Python
- Rust applications
- PostgreSQL
- Docker-related processes
When a framework cannot be identified reliably, PortSpy falls back to the runtime or raw executable.
Processes can be stopped directly from the TUI.
Kill Next.js :3000 (PID 18432)?
y confirm · n cancel · Esc cancel
x requests the kill on the selected listener; confirmation is always explicit.
PortSpy does not silently kill processes merely because they look unnecessary.
Destructive actions require explicit user intent.
Elevated or protected Windows processes refuse termination at the operating-system level; PortSpy reports the failure cleanly as an access error instead of crashing.
Search across listener metadata:
/
Searchable fields include:
- port
- application
- process
- origin
- PID
- command line
Listeners can be sorted by:
- port
- application
- origin
- process
- memory
- uptime
s cycles through the fields, S flips the direction. Sorting is stable: listeners sharing a sort key keep their snapshot order.
A background collector thread rebuilds the system snapshot every 2 seconds and hands it to the UI through a channel, so terminal interaction is never blocked by discovery.
The selected listener is tracked using a stable listener identity rather than a table index.
That means a disappearing process should not make the cursor randomly jump around the interface.
Running PortSpy without a subcommand launches the TUI:
portspyThe core is independent from the TUI so that the same engine also powers scriptable commands:
portspy list # table of local listeners
portspy list --json # machine-readable output
portspy inspect 3000 # details for the process behind :3000
portspy inspect 3000 --json
portspy kill 3000 # confirmation prompt (--yes to skip)Example list --json output:
{
"listeners": [
{
"port": 3000,
"protocol": "tcp",
"address": "127.0.0.1",
"application": { "kind": "nextjs", "confidence": "high" },
"process": {
"pid": 18432,
"name": "node.exe",
"memory_bytes": 641728512,
"started_at": { "secs_since_epoch": 1754678400, "nanos_since_epoch": 0 }
},
"origin": { "kind": "codex", "confidence": "high" }
}
]
}The JSON interface and TUI use the same core domain model.
PortSpy is written in Rust.
The repository is intentionally split into a small number of focused crates:
portspy/
├── crates/
│ ├── portspy-cli/
│ ├── portspy-core/
│ ├── portspy-platform/
│ └── portspy-tui/
│
├── tests/
├── docs/
├── scripts/
└── .github/
The product logic.
Responsibilities include:
- domain models
- snapshot construction
- process ancestry
- application detection
- origin detection
- listener enrichment
- filtering and sorting primitives
The core contains no TUI code and should not depend directly on Windows UI concerns.
Operating-system integration.
The Windows implementation is responsible for things such as:
- TCP listener discovery
- PID ownership
- process metadata
- process creation times
- parent processes
- memory information
- process termination
Windows-native APIs are isolated here.
Any necessary unsafe code must remain inside the platform boundary.
The terminal interface.
Built around Ratatui.
Responsibilities include:
- rendering
- keyboard input
- application state
- selection
- responsive layouts
- details view
- search UI
- confirmation state
- notifications
The TUI does not query Win32 directly.
The executable entry point.
It composes:
platform
↓
core
↓
TUI / CLI output
It also owns command-line argument parsing and non-interactive commands.
PortSpy builds immutable snapshots of the current machine state.
Windows
│
▼
listener discovery
│
▼
unique process IDs
│
▼
process metadata
│
▼
process ancestry
│
▼
application detection
│
▼
origin detection
│
▼
SystemSnapshot
│
├── TUI
└── CLI / JSON
Expensive system discovery is never performed directly inside the rendering path.
A background collector produces snapshots while the TUI remains responsive.
Windows process metadata is factual.
Application and origin detection are inference.
PortSpy does not pretend otherwise.
Processes can disappear during collection.
Permissions can deny metadata access.
PIDs can be recycled.
Command lines may be unavailable.
PortSpy must still produce the best snapshot possible.
One inaccessible process must never break the entire interface.
The repository should remain understandable as it grows.
Responsibilities belong in explicit modules.
Avoid catch-all files such as:
utils.rs
helpers.rs
misc.rs
common.rs
unless they represent a genuinely coherent abstraction.
The rendering layer renders state.
It does not discover ports.
It does not inspect processes.
It does not infer Codex.
It does not decide how Windows processes should be killed.
PortSpy starts with four crates.
A new crate should only exist when there is a real architectural boundary that justifies it.
PortSpy is Windows-first.
TCP listener discovery is designed around native Windows networking APIs such as:
GetExtendedTcpTable
Process metadata is collected through appropriate Win32 APIs.
The first target is:
Windows 11
x86_64-pc-windows-msvc
WSL2 awareness will progressively improve after the initial Windows foundation is stable.
PortSpy can terminate local processes, so destructive behavior is deliberately conservative.
Principles:
- killing requires explicit confirmation
- process disappearance is handled safely
- permission failures do not crash PortSpy
- critical Windows processes are protected
- no automatic duplicate termination
- no background daemon in
v0.1 - no remote API in
v0.1
PortSpy is a local developer tool, not a process supervisor.
At least not yet.
- Windows 11
- Rust stable
- Visual Studio Build Tools / MSVC toolchain
- Git
Verify:
rustc --version
cargo --versionClone the repository:
git clone https://github.com/<org-or-user>/portspy.git
cd portspyBuild:
cargo build --workspaceRun:
cargo run -p portspy-cliRelease build:
cargo build --releaseEvery change should pass:
cargo fmt --all -- --check
cargo check --workspace
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspaceAdditional dependency and supply-chain checks may use:
cargo deny checkProduction code should avoid:
unwrap()
expect()
todo!()
dbg!()unless there is a deliberate and documented reason.
Unsafe code belongs only in the platform implementation where native Windows APIs require it.
PortSpy uses several layers of testing.
Used for deterministic logic such as:
- origin detection
- application detection
- sorting
- filtering
- listener identity
- process ancestry reconstruction
Example:
WindowsTerminal.exe
└── pwsh.exe
└── codex.exe
└── node.exe
must resolve to:
Origin: Codex
Synthetic process trees allow detection logic to be tested without depending on the developer's machine.
Integration tests can create real temporary listeners:
TcpListener::bind("127.0.0.1:0")The operating system assigns an available port.
PortSpy must then discover that listener and associate it with the expected process.
Tests should never depend on hardcoded development ports such as 3000.
System inspection is inherently racy.
Tests must account for processes that:
- appear
- disappear
- restart
- change state
- become inaccessible
while a snapshot is being created.
PortSpy should degrade gracefully rather than panic.
The first usable release.
- Windows TCP listener discovery
- IPv4
- IPv6
- PID resolution
- process metadata
- process creation time
- process ancestry
- application detection
- origin detection
- Codex detection
- Claude Code detection
- Cursor detection
- VS Code detection
- Windows Terminal detection
- PowerShell detection
- WSL awareness
- responsive Ratatui interface
- listener list
- inspect view
- search
- sorting
- stable auto-refresh
- process kill
- kill confirmation
- JSON output
- Windows CI
- standalone release binary
Deeper process intelligence.
- UDP listeners
- process-tree termination
- improved WSL2 attribution
- project detection
- working-directory detection improvements
- duplicate dev-server detection
- orphan process detection
- improved runtime detection
Development environment awareness.
- Docker awareness
- resource history
- CPU trends
- memory trends
- service grouping
- project grouping
Agent integration.
- stable agent-facing CLI
- Codex integration
- Claude Code integration
- agent skills
- MCP server
- shared runtime awareness
Long-term direction.
- stable CLI contract
- stable JSON schema
- Windows production support
- Linux support
- macOS support
- extensible detection rules
- mature WSL2 support
PortSpy v0.1 is deliberately not:
- a port scanner
- a network pentesting tool
- a firewall manager
- a Docker manager
- a system monitor replacement
- a process supervisor
- a daemon
- a cloud service
- an AI wrapper
It is a focused local developer tool.
Find the port.
Understand the process.
Know where it came from.
Stop it when you want.
PortSpy is still early, so architectural consistency matters more than raw feature count.
Before contributing:
- keep changes scoped
- do not mix unrelated refactors with features
- add tests for new detection rules
- keep Windows-specific code inside the platform boundary
- keep business logic out of the TUI
- update architecture documentation when changing a major boundary
- run the complete quality gate before submitting a pull request
Bug reports involving process detection should ideally include:
Port
Process name
PID
Command line if safe to share
Parent process chain
Windows version
Terminal / IDE / agent involved
Do not include secrets, tokens, environment variables, or private command-line arguments in public issues.
Significant architectural decisions are documented under:
docs/adr/
Initial decisions include:
ADR-001 Workspace and crate boundaries
ADR-002 Windows-native listener discovery
ADR-003 Origin and application inference
ADR-004 Snapshot-driven TUI architecture
ADRs exist for decisions that would be expensive to reverse later.
If you discover a security issue in PortSpy, please do not disclose it publicly before maintainers have had a reasonable opportunity to investigate.
See:
SECURITY.md
for the project's disclosure process.
License information will be available in:
LICENSE
PortSpy
Know what's using your localhost.
portspy.dev