defmodule AiDebug.PortFinder do @moduledoc """ Dynamically finds available ports to avoid conflicts with user applications. This was implemented after dogfooding revealed port 4000 conflicts with many development servers. """ # AI-Debug reserved port range: 8200-8299 (safe from common dev ports) @default_port 8200 @max_attempts 100 @doc """ Find an available port starting from the default port. ## Examples iex> AiDebug.PortFinder.find_available_port() {:ok, 8200} iex> AiDebug.PortFinder.find_available_port(8250) {:ok, 8250} """ def find_available_port(start_port \\ @default_port) do find_port(start_port, 0) end defp find_port(_port, attempts) when attempts >= @max_attempts do {:error, :no_available_port} end defp find_port(port, attempts) do case port_available?(port) do true -> {:ok, port} false -> find_port(port + 1, attempts + 1) end end @doc """ Check if a specific port is available. """ def port_available?(port) do case :gen_tcp.listen(port, [:binary, {:active, false}, {:reuseaddr, true}]) do {:ok, socket} -> :gen_tcp.close(socket) true {:error, :eaddrinuse} -> false {:error, _} -> false end end @doc """ Get a configuration-friendly port setting. Returns a port configuration that can be used in Phoenix endpoint config. """ def get_phoenix_port_config() do case find_available_port() do {:ok, port} -> [ip: {127, 0, 0, 1}, port: port] {:error, _} -> # Fallback to default [ip: {127, 0, 0, 1}, port: @default_port] end end @doc """ Print available port information for debugging. """ def debug_ports(range \\ 8200..8210) do IO.puts("Port availability check:") Enum.each(range, fn port -> status = if port_available?(port), do: "✅ Available", else: "❌ In use" IO.puts(" Port #{port}: #{status}") end) end end