Skip to content

PRD: Linux-first TUI for local port ownership and control #1

@zoldyzdk

Description

@zoldyzdk

Problem Statement

Developers frequently run multiple local services on their machines, such as frontend dev servers, backend APIs, hot-reload supervisors, and other tools that bind to TCP ports. When a needed port is already in use, figuring out what owns it and how to safely stop it often requires leaving the current workflow, remembering platform-specific shell commands, interpreting noisy output, and manually sending signals to processes.

This creates friction during normal development work. The developer's immediate need is usually not full system observability, but fast, trustworthy answers to a narrow question: what process owns this port right now, and can I free it quickly without guessing?

The problem is amplified when the process name is unclear, when the owning process has insufficient permission for control, or when a process is restarted automatically by a supervisor and re-binds to the same port. Existing shell-based workflows are powerful, but they are not optimized for fast visual scanning, low-friction keyboard navigation, or clear stop/retry feedback.

Solution

Build a Linux-first terminal UI application in Go that gives developers a fast, attractive, keyboard-first view of local TCP listening ports and the host processes that own them. The app should prioritize trustworthy port ownership detection and a clear, conservative stop workflow over broad observability features.

From the user's perspective, the app opens into a compact, auto-refreshing table of listening ports. The user can navigate with arrow keys, search by port, command, or executable name, inspect richer metadata in a details pane, and stop a selected process with explicit confirmations and visible feedback. The app should try a graceful stop first, verify whether the port is actually released, and offer a force-kill action only when necessary. If a port quickly reappears, the app should warn that the process was likely restarted by a supervisor.

The visual design should feel polished and modern using the Charm ecosystem, especially Bubble Tea, Bubbles, and Lip Gloss. The UI should use a dark theme with semantic status colors, subtle borders, strong focus states, and a compact layout that remains usable on common terminal sizes.

The app should be installable as a single binary via go install github.com/zoldyzdk/sniff@latest.

User Stories

  1. As a local developer, I want to open the app and immediately see which TCP ports are listening, so that I can quickly understand what is running on my machine.
  2. As a local developer, I want the default view sorted by port number, so that I can quickly scan for the specific port I care about.
  3. As a local developer, I want the UI to auto-refresh every few seconds, so that the list stays accurate while I start and stop services outside the app.
  4. As a local developer, I want a manual refresh action, so that I can immediately re-check state without waiting for the next interval.
  5. As a local developer, I want to navigate the table with the keyboard, so that I can stay fully inside the terminal workflow.
  6. As a local developer, I want the table to be compact by default, so that the first screen is easy to scan.
  7. As a local developer, I want a search field above the table, so that I can narrow results quickly.
  8. As a local developer, I want search to match port numbers, process commands, and executable names, so that I can find services even when I do not remember the exact port.
  9. As a local developer, I want search results to update quickly, so that filtering feels responsive.
  10. As a local developer, I want the selected row to show a details pane, so that I can inspect more context before taking action.
  11. As a local developer, I want to see the PID for the selected process, so that I know exactly which process the app is talking about.
  12. As a local developer, I want to see the full command line, so that I can distinguish similar-looking services.
  13. As a local developer, I want to see the executable name, so that I can identify what binary is actually listening.
  14. As a local developer, I want to see the current user for the process, so that I understand whether I likely have permission to control it.
  15. As a local developer, I want to see the working directory when available, so that I can identify which project owns a service.
  16. As a local developer, I want to see process start time when available, so that I can reason about what started recently.
  17. As a local developer, I want the app to use semantic colors and clear status styling, so that state is understandable at a glance.
  18. As a local developer, I want locked or permission-restricted processes to be visually distinct, so that I do not waste time attempting unsupported actions.
  19. As a local developer, I want to stop a selected process from the UI, so that I can free a conflicted port without leaving the app.
  20. As a local developer, I want stop actions to require confirmation, so that I do not accidentally terminate the wrong service.
  21. As a local developer, I want the default stop behavior to be graceful, so that I can shut down services safely.
  22. As a local developer, I want the app to verify whether the port was actually released after a stop attempt, so that I do not have to guess whether the action worked.
  23. As a local developer, I want the app to offer a force-kill action only after a graceful stop fails or is insufficient, so that destructive behavior remains explicit.
  24. As a local developer, I want clear inline feedback after a stop action, so that I can immediately understand success, failure, or timeout.
  25. As a local developer, I want a small recent-actions log, so that I can trust what the app just attempted.
  26. As a local developer, I want the selected row to remain in context after a failed stop attempt, so that I can recover without losing my place.
  27. As a local developer, I want the app to warn me if a port quickly reappears after termination, so that I understand a supervisor may have restarted it.
  28. As a local developer, I want the app to treat the listening PID as the source of truth, so that ownership remains predictable.
  29. As a local developer, I want the app to avoid clever but unreliable guesses about the “real” app process, so that the UI stays trustworthy.
  30. As a local developer, I want inaccessible processes to remain visible in the table, so that I still know what is blocking my port.
  31. As a local developer, I want the details pane to explain when elevated privileges may be required, so that I know what to do next.
  32. As a local developer, I want the app to avoid prompting for sudo inside the interface, so that privilege boundaries remain explicit and unsurprising.
  33. As a local developer, I want the app to focus the table on startup, so that I can start navigating immediately.
  34. As a local developer, I want a help footer showing shortcuts, so that the interface stays discoverable.
  35. As a local developer, I want the details pane to collapse gracefully on small terminals, so that the app remains usable in constrained layouts.
  36. As a local developer, I want the app to use a single compiled binary, so that installation and updates are simple.
  37. As a local developer, I want the app to rely primarily on native Linux process and socket information, so that it does not depend on external shell tools being present.
  38. As a local developer, I want the app to remain useful even when Docker-launched host processes are involved, so that common local development setups are still understandable.
  39. As a local developer, I want Docker-related processes to be labeled when detectable from host metadata, so that I get helpful context without requiring full Docker integration.
  40. As a local developer, I want refresh and action feedback to feel stable rather than flickery, so that the UI feels polished and trustworthy.
  41. As a local developer, I want errors to be explained in plain language, so that I do not need to infer what a failed signal or permission issue means.
  42. As a local developer, I want the app to optimize for identifying and freeing a conflicted port in under 10 seconds, so that it solves the real interruption in my workflow.
  43. As a maintainer, I want the internal architecture to separate discovery, enrichment, actions, refresh coordination, and UI state, so that the app stays testable and maintainable.
  44. As a maintainer, I want the most complex system interactions encapsulated behind deep modules, so that Linux-specific complexity does not leak into the TUI layer.
  45. As a maintainer, I want behavior-focused tests around discovery and stop workflows, so that refactors do not break the core promise of the tool.

Implementation Decisions

  • The product is a Linux-first TUI focused on local developers rather than a general-purpose system monitoring tool.
  • The first release targets host processes only.
  • The first release covers TCP listening ports only.
  • The app uses the listening PID as the source of truth for ownership.
  • The main workflow is fast port ownership lookup and stop/control, not broad monitoring or observability.
  • The app auto-refreshes on a short interval and also supports manual refresh.
  • The default sort is numeric port ascending.
  • The main screen is a compact table with a search field above it and a help footer below it.
  • The default startup focus is the table.
  • Search matches port, command, and executable name.
  • The selected row exposes a rich details pane containing PID, full command, executable, user, working directory when available, start time when available, and recent stop outcome.
  • The interface uses a dark theme with semantic colors, subtle borders, and clear focus/selection states.
  • The visual stack is Bubble Tea for the state/update loop, Bubbles for shared UI components, and Lip Gloss for layout and styling.
  • The app is distributed as a single Go binary installable with go install.
  • Core port and process discovery should be implemented in Go via Linux /proc and kernel socket data, not by depending on tools like lsof or ss for the primary path.
  • Docker is not a first-class integration in v1, but host processes may be labeled as Docker-related when detectable from command or cgroup metadata.
  • Stop behavior is conservative: confirm before stop, send SIGTERM, wait briefly while showing progress, verify whether the port is released, and only then offer explicit SIGKILL.
  • The app should not silently escalate from graceful termination to force kill.
  • The app should not attempt in-app privilege escalation. Permission restrictions should be communicated clearly in the UI.
  • Root-owned or otherwise restricted processes remain visible, but their actions are guarded or disabled as appropriate.
  • If a port quickly rebinds after a stop attempt, the app should surface a likely respawn/restart warning.
  • A small recent-actions log or status history is included in the UI to improve trust.
  • No configuration file is required in v1.
  • The architecture should be organized around the following deep modules:
  • SocketDiscovery for mapping listening TCP ports to owning processes.
  • ProcessResolver for enriching process metadata used by the UI.
  • PortActionRunner for executing stop actions and verifying outcomes.
  • RefreshCoordinator for periodic refresh, state reconciliation, and quick-rebind detection.
  • TUIModel for Bubble Tea state, interaction flow, and rendering integration.
  • ThemeSystem for semantic style tokens and layout primitives.

Testing Decisions

  • Good tests should validate externally observable behavior and outcomes rather than internal implementation details.
  • Tests should focus on the tool’s main promise: accurate port ownership reporting, reliable stop behavior, clear state transitions, and predictable handling of permission and respawn edge cases.
  • The highest-priority modules for v1 test coverage are SocketDiscovery, ProcessResolver, PortActionRunner, and RefreshCoordinator.
  • TUIModel should have focused behavior tests where they materially protect keyboard flow, filtering behavior, and state transitions, but not exhaustive snapshot-style tests for every visual detail.
  • ThemeSystem should be tested lightly, only where semantic style mappings protect user-critical meaning such as warning, success, selection, or locked states.
  • Tests should use isolated fixtures, controlled subprocesses, or fake adapters where needed to avoid relying on unstable machine-specific state.
  • Discovery tests should verify that listening ports are mapped correctly, filtered to the supported scope, and surfaced consistently when metadata is partially unavailable.
  • Action tests should verify graceful-stop success, graceful-stop timeout, explicit force-kill flow, permission-denied outcomes, and quick-rebind detection.
  • Refresh coordination tests should verify that periodic updates and action results reconcile into a stable UI state without stale ownership data.
  • Since the repository is currently greenfield, prior art within the codebase does not yet exist. The initial test style should therefore establish the project standard: behavior-first tests around deep modules and minimal coupling to rendering internals.

Out of Scope

  • Windows support.
  • macOS support.
  • Full Docker integration or container lifecycle management.
  • Kubernetes integration.
  • Remote host monitoring.
  • UDP support.
  • Established connection monitoring.
  • Full observability dashboards such as charts, historical metrics, or long-term resource monitoring.
  • Automatic supervisor detection and targeted termination of parent supervisors.
  • In-app privilege escalation or sudo prompting.
  • A daemon/background service architecture.
  • A user configuration file in the first release.
  • Broad plugin or extensibility systems.

Further Notes

  • The success metric for v1 is that a developer can open the app, identify who owns a conflicted port, and free that port in under 10 seconds without leaving the terminal.
  • Reliability and trust are the primary release criteria. Visual polish is important and should be a visible strength of the product, but it must not come at the expense of correct ownership detection or dependable stop behavior.
  • The first version should intentionally solve one problem well rather than expand into generic machine monitoring.
  • The product should feel delightful and modern, but the delight should come from both appearance and confidence in the workflow.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions