netbeagle 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -10,15 +10,16 @@
10
10
  <a href="https://www.npmjs.com/package/netbeagle"><img src="https://img.shields.io/npm/v/netbeagle?color=a8521a" alt="npm version"></a>
11
11
  <a href="https://github.com/michelsalib/netbeagle/actions/workflows/ci.yml"><img src="https://github.com/michelsalib/netbeagle/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
12
12
  <img src="https://img.shields.io/node/v/netbeagle" alt="Node version">
13
- <img src="https://img.shields.io/badge/platform-Linux%20%7C%20WSL-blue" alt="Linux and WSL">
13
+ <img src="https://img.shields.io/badge/platform-Linux%20%7C%20macOS%20%7C%20Windows-blue" alt="Linux, macOS and Windows">
14
14
  <a href="LICENSE"><img src="https://img.shields.io/npm/l/netbeagle" alt="MIT license"></a>
15
15
  <a href="https://scorecard.dev/viewer/?uri=github.com/michelsalib/netbeagle"><img src="https://api.scorecard.dev/projects/github.com/michelsalib/netbeagle/badge" alt="OpenSSF Scorecard"></a>
16
16
  </p>
17
17
 
18
18
  What is on port 5432, a local postgres or the one in docker? Can my laptop's neighbours reach it, or only
19
19
  this machine? Which process keeps a connection open to that server, and through which interface and
20
- gateway does it leave? netbeagle answers those in one command, by reading what the kernel already
21
- knows (`/proc`, `/sys`) and asking docker which container sits behind each published port.
20
+ gateway does it leave? netbeagle answers those in one command, by reading what the system already
21
+ knows (`/proc` and `/sys` on Linux, `netstat` and `lsof` on macOS, PowerShell on Windows) and asking
22
+ docker which container sits behind each published port.
22
23
 
23
24
  **Try it, nothing to install:**
24
25
 
@@ -68,8 +69,8 @@ dns 127.0.0.53 (local resolver)
68
69
 
69
70
  ## Install
70
71
 
71
- netbeagle is a single JavaScript file that runs on Node ≥ 20 or Bun, on Linux and WSL. Without either,
72
- the installer fetches a standalone binary instead (x64 or arm64), which needs nothing.
72
+ netbeagle is a single JavaScript file that runs on Node ≥ 20 or Bun, on Linux (WSL included), macOS and
73
+ Windows. Without either, the installers fetch a standalone binary instead, which needs nothing.
73
74
 
74
75
  ```sh
75
76
  npx netbeagle map # or: bunx netbeagle map, nothing to install
@@ -80,16 +81,32 @@ curl -fsSL https://github.com/michelsalib/netbeagle/releases/latest/download/ins
80
81
  To pin a version, or to read the script before running it:
81
82
 
82
83
  ```sh
83
- curl -fsSL https://github.com/michelsalib/netbeagle/releases/download/v0.1.0/install.sh -o install.sh
84
+ curl -fsSL https://github.com/michelsalib/netbeagle/releases/download/v0.2.0/install.sh -o install.sh
84
85
  less install.sh && sh install.sh
85
86
  gh attestation verify install.sh -R michelsalib/netbeagle # optional: proves GitHub Actions built it
86
87
  ```
87
88
 
88
- The binaries are release files too (`netbeagle-linux-x64`, `netbeagle-linux-arm64`), listed in
89
- `SHA256SUMS` and attested like the rest; see [SECURITY.md](SECURITY.md#verifying-a-release).
89
+ On Windows, in PowerShell:
90
90
 
91
- Without root, netbeagle can't see other users' processes, so their sockets show the user only;
92
- `sudo netbeagle` shows them all. netbeagle only reads: see [what it does with your machine](SECURITY.md#what-netbeagle-does-with-your-machine).
91
+ ```powershell
92
+ irm https://github.com/michelsalib/netbeagle/releases/latest/download/install.ps1 | iex
93
+ ```
94
+
95
+ `install.sh` puts `netbeagle` in `~/.local/bin` (`NETBEAGLE_INSTALL_DIR` to change it, `NETBEAGLE_VERSION=0.2.0`
96
+ to pin a version), checks it against the release's `SHA256SUMS`, and runs it on Node, or on Bun when there
97
+ is no Node ≥ 20. With neither, or with `NETBEAGLE_BINARY=1`, it installs the standalone binary for your
98
+ system (Linux and macOS, x64 and arm64). `install.ps1` does the same on Windows, in
99
+ `%LOCALAPPDATA%\Programs\netbeagle`: `netbeagle.mjs` and a `netbeagle.cmd` launcher, or `netbeagle.exe`.
100
+ Neither edits your `PATH`: they print what to run.
101
+
102
+ The binaries are release files too (`netbeagle-linux-x64`, `netbeagle-linux-arm64`, `netbeagle-darwin-x64`,
103
+ `netbeagle-darwin-arm64`, `netbeagle-windows-x64.exe`), listed in `SHA256SUMS` and attested like the rest;
104
+ see [SECURITY.md](SECURITY.md#verifying-a-release).
105
+
106
+ On Linux and macOS, without root, netbeagle can't see other users' processes, so their sockets show no
107
+ process; `sudo netbeagle` shows them all. Windows names every socket's process, but shows other users'
108
+ command lines only in an administrator terminal. netbeagle only reads: see
109
+ [what it does with your machine](SECURITY.md#what-netbeagle-does-with-your-machine).
93
110
 
94
111
  ## Commands
95
112
 
@@ -110,21 +127,36 @@ lines are shown with the values of `--token=`, `--password` and the like masked
110
127
 
111
128
  ## How it works
112
129
 
113
- - **Sockets** come from `/proc/net/tcp`, `tcp6`, `udp` and `udp6`; IPv4 clients of a socket bound to `::`
130
+ Each system is read its own way, into the same picture, so every command works the same everywhere.
131
+
132
+ - **Linux**: sockets from `/proc/net/tcp`, `tcp6`, `udp` and `udp6`; IPv4 clients of a socket bound to `::`
114
133
  (`::ffff:1.2.3.4`) are shown as IPv4. Each socket's process is found through the `socket:[inode]` links
115
134
  in `/proc/PID/fd`. Without root, other users' processes can't be read: their sockets show the user
116
- only, and `sudo netbeagle` shows them all.
117
- - **Interfaces** come from `/sys/class/net` (kind, state, bridge and its ports, traffic) and their
118
- addresses from `getifaddrs`. **Routes** from the kernel's main table, `/proc/net/route` and
119
- `/proc/net/ipv6_route`; `route` picks the longest matching prefix, then the lowest metric, as the
120
- kernel does (policy routing, `ip rule`, is not replayed).
135
+ only, and `sudo netbeagle` shows them all. Interfaces from `/sys/class/net` (kind, state, bridge and
136
+ its ports, traffic) and their addresses from `getifaddrs`; routes from the kernel's main table,
137
+ `/proc/net/route` and `/proc/net/ipv6_route`.
138
+ - **macOS**: every user's sockets from `netstat -an` (`tcp46` for a socket taking both families), and
139
+ the process behind each from `lsof`: without root, only your own, so other users' show no process and
140
+ `sudo netbeagle` shows them all. Interfaces from `ifconfig -a` (bridge members included), their traffic
141
+ from `netstat -ibn`, which one is Wi-Fi from `networksetup`; routes from `netstat -rn`, without the
142
+ ARP entries and interface-scoped copies; processes from `ps`.
143
+ - **Windows**: one PowerShell call. Sockets and their processes from `Get-NetTCPConnection` and
144
+ `Get-NetUDPEndpoint` (`netstat` translates its states into the system's language; these don't),
145
+ interfaces, addresses, traffic and DNS servers from .NET's `NetworkInterface`, routes from `Get-NetRoute`
146
+ (their metric plus the interface's, as Windows compares them), command lines from `Win32_Process`.
147
+ It takes a couple of seconds, PowerShell's start included.
148
+ - `route` picks the longest matching prefix, then the lowest metric, as the system does (policy
149
+ routing, `ip rule` on Linux, is not replayed).
121
150
  - **Containers**: `docker inspect` of the running containers gives their networks (and the host bridge
122
151
  each uses, `docker0` or `br-<network id>`) and their published ports. A published port belongs to
123
152
  its container, whether `docker-proxy` holds a socket for it or the firewall forwards it with none.
153
+ - **Containers on macOS and Windows**: Docker Desktop runs them in a VM, so their networks are not on this
154
+ machine; a published port shows its container all the same.
124
155
  - **Who reaches what**: a socket on `127.0.0.1`, `::1` or another address of the loopback is reachable
125
- from this machine only; on `0.0.0.0` or `::`, from every interface (`::` takes IPv4 too, as
126
- `net.ipv6.bindv6only` is 0 by default); on one address, from the interface holding it. Firewall rules
127
- are not taken into account.
156
+ from this machine only; on `0.0.0.0` or `::`, from every interface; on one address, from the interface
157
+ holding it. A socket on `::` takes IPv4 too by default on Linux (`net.ipv6.bindv6only` is 0) and on
158
+ macOS (`tcp46`), and IPv6 only on Windows unless its program asks for both: those say
159
+ `every interface, IPv6 only`. Firewall rules are not taken into account.
128
160
  - **WSL**: Windows forwards its own `localhost` to ports bound to `127.0.0.1` or every interface in the
129
161
  WSL VM, so those say `+ Windows`.
130
162
  - Connections between two processes of this machine are matched end to end, so `curl 400 → :5000 node
@@ -132,9 +164,9 @@ lines are shown with the values of `--token=`, `--password` and the like masked
132
164
 
133
165
  ## Status
134
166
 
135
- Linux, including WSL. Planned: macOS (`lsof`, `netstat`), Windows (`Get-NetTCPConnection`),
136
- `netbeagle doctor` (services exposed on every interface, ports bound twice, DNS servers with no
137
- route), firewall rules, and network namespaces (inside containers).
167
+ Linux (WSL included), macOS and Windows. Planned: `netbeagle doctor` (services exposed on every
168
+ interface, ports bound twice, DNS servers with no route), firewall rules, network namespaces (inside
169
+ containers), and a Homebrew tap and a Scoop bucket.
138
170
 
139
171
  ## The pack
140
172
 
@@ -151,14 +183,15 @@ nothing to configure.
151
183
  ```sh
152
184
  bun install
153
185
  bun run dev -- map # run from source
154
- bun test # unit tests on test/fixtures/root, plus the built CLI on node on this machine
186
+ bun test # unit tests on the fixtures of each system, plus the built CLI on node on this machine
155
187
  bun run typecheck
156
188
  bun run lint # Biome: formatting and lint
157
189
  bun run build # dist/netbeagle.js, plain JS for node >= 20
158
190
  ```
159
191
 
160
- `test/fixtures/root` is a small machine as `/proc`, `/sys` and `/etc` show it, written by
161
- `scripts/fixture.py` (its docstring describes it). Source uses only `node:` APIs, so the build runs on
192
+ `test/fixtures/root` is a small Linux machine as `/proc`, `/sys` and `/etc` show it, written by
193
+ `scripts/fixture.py` (its docstring describes it); `test/fixtures/macos` holds what its commands print
194
+ on a Mac, and `test/fixtures/windows` what netbeagle's PowerShell script prints on Windows. Source uses only `node:` APIs, so the build runs on
162
195
  both Node and Bun.
163
196
 
164
197
  See [CONTRIBUTING.md](CONTRIBUTING.md) for pull requests and releases.
package/dist/netbeagle.js CHANGED
@@ -1,19 +1,55 @@
1
1
  #!/usr/bin/env node
2
- import{lookup as Nt}from"node:dns/promises";import{isIP as Et}from"node:net";import{parseArgs as Ct}from"node:util";var E={name:"netbeagle",version:"0.1.0",description:"See which process listens on each port, which interfaces reach it, and how everything on your machine connects",keywords:["network","ports","netstat","ss","lsof","interfaces","docker","routes"],license:"MIT",author:"Michel Salib",repository:{type:"git",url:"git+https://github.com/michelsalib/netbeagle.git"},homepage:"https://github.com/michelsalib/netbeagle#readme",bugs:"https://github.com/michelsalib/netbeagle/issues",type:"module",bin:{netbeagle:"dist/netbeagle.js"},files:["dist/netbeagle.js"],engines:{node:">=20"},scripts:{dev:"bun src/cli.ts",build:"bun build src/cli.ts --target=node --minify --outfile dist/netbeagle.js","build:bin":"bun scripts/build-binaries.ts",test:"bun test --timeout 30000",typecheck:"tsc",lint:"biome check",prepublishOnly:"bun run typecheck && bun run lint && bun run build && bun test --timeout 30000"},devDependencies:{"@biomejs/biome":"2.5.15","@types/bun":"^1.4.2","@types/node":"^26.6.4",typescript:"^7.0.2"}};import{isIPv4 as fe,isIPv6 as me}from"node:net";function K(e){if(fe(e))return e.split(".").map(Number);if(!me(e))return;let t=e.replace(/%.*$/,""),n=t.match(/(\d+\.\d+\.\d+\.\d+)$/);if(n?.[1]){let[a,d,u,p]=n[1].split(".").map(Number);t=`${t.slice(0,-n[1].length)}${(a<<8|d).toString(16)}:${(u<<8|p).toString(16)}`}let[o="",s]=t.split("::"),r=o?o.split(":"):[],i=s?s.split(":"):[];return(s===void 0?r:[...r,...Array(8-r.length-i.length).fill("0"),...i]).flatMap((a)=>{let d=Number.parseInt(a,16);return[d>>8,d&255]})}var he=(e)=>fe(e)?4:me(e)?6:void 0;function Y(e){let t=[];for(let r=0;r<16;r+=2)t.push((e[r]??0)<<8|(e[r+1]??0));let n=-1,o=1;for(let r=0;r<8;){if(t[r]!==0){r++;continue}let i=r;while(i<8&&t[i]===0)i++;if(i-r>o)n=r,o=i-r;r=i}let s=t.map((r)=>r.toString(16));if(n<0)return s.join(":");return`${s.slice(0,n).join(":")}::${s.slice(n+o).join(":")}`}function z(e){let t=Number.parseInt(e,16);return[t&255,t>>>8&255,t>>>16&255,t>>>24&255]}var S=(e)=>z(e).join(".");function ge(e){let t=[];for(let n=0;n<32;n+=8)t.push(...z(e.slice(n,n+8)));if(t.slice(0,10).every((n)=>n===0)&&t[10]===255&&t[11]===255)return{family:4,address:t.slice(12).join(".")};return{family:6,address:Y(t)}}function q(e){let t=[];for(let n=0;n<32;n+=2)t.push(Number.parseInt(e.slice(n,n+2),16));return Y(t)}function X(e,t,n){let o=K(e),s=K(t);if(!o||!s||o.length!==s.length)return!1;for(let r=0;r<n;r++){let i=r>>3,c=128>>(r&7);if(((o[i]??0)&c)!==((s[i]??0)&c))return!1}return!0}function be(e){let n=(K(e.address)??[]).map((o,s)=>{let r=Math.max(0,Math.min(8,e.prefix-s*8));return o&(255<<8-r&255)});return e.family===4?n.join("."):Y(n)}var k=(e)=>e==="0.0.0.0"||e==="::",v=(e)=>e==="::1"||e.startsWith("127."),ke=(e)=>e.startsWith("169.254.")||/^fe[89ab][0-9a-f]:/i.test(e),I=(e)=>e.address.includes(":")?`[${e.address}]:${e.port}`:`${e.address}:${e.port}`,we=(e)=>z(e).reduce((t,n)=>t+n.toString(2).replaceAll("0","").length,0);var Q=(e,t)=>e.users[t]??String(t),Z=(e,t)=>t===void 0?void 0:e.processes.find((n)=>n.pid===t),C=(e,t)=>e.interfaces.find((n)=>n.addresses.some((o)=>o.address===t));function ye(e,t){for(let n of e.containers)for(let o of n.networks)if(o.ip===t)return{container:n,network:o};return}function j(e,t){let n=he(t)??4,o={dest:t,family:n};if(v(t))return{...o,kind:"loopback",iface:"lo",source:t};let s=C(e,t);if(s)return{...o,kind:"local",iface:s.name,source:t};let r=e.routes.filter((p)=>p.family===n&&X(t,p.dest,p.prefix)).sort((p,f)=>f.prefix-p.prefix||p.metric-f.metric)[0];if(!r)return{...o,kind:"unreachable"};let i=e.interfaces.find((p)=>p.name===r.iface),c=r.gateway??t,a=i?.addresses.filter((p)=>p.family===n)??[],d=a.find((p)=>X(c,be(p),p.prefix))?.address??a.find((p)=>!ke(p.address))?.address??a[0]?.address,u=ye(e,t);return{...o,kind:u?"container":r.gateway?"gateway":"network",iface:r.iface,gateway:r.gateway,route:r,source:d,container:u?.container}}function ot(e,t){let n=(r)=>v(r)||C(e,r)?.kind==="loopback";if(t.every(n))return{scope:"local",interfaces:["lo"]};let o=e.interfaces.filter((r)=>r.state!=="down"&&r.addresses.length);if(t.some(k)){let r=new Set(t.some((i)=>i==="::")?[4,6]:[4]);return{scope:"everywhere",interfaces:o.filter((i)=>!i.master).filter((i)=>i.addresses.some((c)=>r.has(c.family))).map((i)=>i.name)}}let s=new Set;for(let r of t){if(v(r))s.add("lo");let i=C(e,r);if(i)s.add(i.name)}return{scope:"interfaces",interfaces:[...s]}}var rt=(e,t,n,o)=>e.proto===t&&e.hostPort===n&&(o===void 0||k(e.hostIp)||k(o)||e.hostIp===o);function st(e,t,n,o){for(let s of e.containers)for(let r of s.ports)if(rt(r,t,n,o))return{container:s,published:r};return}var $e=(e)=>e.state==="LISTEN"||e.state==="UNCONN"&&e.local.port!==0;function R(e){let t=new Map;for(let o of e.sockets.filter($e)){let s=st(e,o.proto,o.local.port,o.local.address),r=s?`c${s.container.id}`:o.pid!==void 0?`p${o.pid}`:`u${o.uid}`,i=`${o.proto}/${o.local.port}/${r}`,c=t.get(i);if(!c)c={proto:o.proto,port:o.local.port,addresses:[],reach:{scope:"local",interfaces:[]},process:Z(e,o.pid),user:Q(e,o.uid),container:s?.container,published:s?.published,sockets:[],clients:[]},t.set(i,c);if(c.sockets.push(o),!c.addresses.includes(o.local.address))c.addresses.push(o.local.address)}for(let o of e.containers)for(let s of o.ports){if([...t.values()].some((a)=>a.proto===s.proto&&a.port===s.hostPort&&a.container?.id===o.id))continue;let i=`${s.proto}/${s.hostPort}/c${o.id}`,c=t.get(i);if(c){if(!c.addresses.includes(s.hostIp))c.addresses.push(s.hostIp);continue}t.set(i,{proto:s.proto,port:s.hostPort,addresses:[s.hostIp],reach:{scope:"local",interfaces:[]},user:"root",container:o,published:s,sockets:[],clients:[]})}let n=[...t.values()];for(let o of n)o.reach=ot(e,o.addresses);for(let o of P(e,n))if(o.direction==="in")O(n,o.proto,o.local)?.clients.push(o);return n.sort((o,s)=>o.port-s.port||o.proto.localeCompare(s.proto))}function O(e,t,n){return e.find((o)=>o.proto===t&&o.port===n.port&&o.addresses.some((s)=>s===n.address||k(s)))}function P(e,t){let n=t??R(e),o=[];for(let s of e.sockets){if($e(s)||s.remote.port===0)continue;let r=O(n,s.proto,s.local)?"in":"out";o.push({proto:s.proto,local:s.local,remote:s.remote,state:s.state,process:Z(e,s.pid),user:s.inode===0?void 0:Q(e,s.uid),direction:r,peer:it(e,n,s)})}return o}function it(e,t,n){let o=n.remote.address;if(v(o)||C(e,o)){let r=e.sockets.find((c)=>c!==n&&c.proto===n.proto&&c.local.port===n.remote.port&&c.remote.port===n.local.port&&c.local.address===o&&c.remote.address===n.local.address),i=O(t,n.proto,n.remote);return{kind:"local",process:Z(e,r?.pid),user:r?Q(e,r.uid):void 0,listener:i&&O(t,n.proto,n.local)!==i?i:void 0}}let s=ye(e,o);if(s)return{kind:"container",container:s.container,network:s.network.name};return{kind:"remote",route:j(e,o)}}function xe(e,t){return{port:t,listeners:R(e).filter((n)=>n.port===t),outbound:P(e).filter((n)=>n.direction==="out"&&n.remote.port===t&&!(n.peer.kind==="local"&&n.peer.listener?.port===t))}}function ee(e){let t=R(e),n=P(e,t).filter((r)=>r.state==="ESTABLISHED"),o=[],s=[];for(let r of e.interfaces){if(r.master)continue;let i=e.containers.flatMap((d)=>d.networks.filter((u)=>u.bridge===r.name).map((u)=>({container:d,ip:u.ip}))),c={iface:r,routes:e.routes.filter((d)=>d.iface===r.name),listeners:t.filter((d)=>d.reach.scope!=="everywhere"&&d.reach.interfaces.includes(r.name)),outbound:n.filter((d)=>d.direction==="out"&&d.peer.kind==="remote"&&d.peer.route.iface===r.name),local:r.kind==="loopback"?n.filter((d)=>d.peer.kind==="local"&&d.direction==="out"):[],containers:i},a=!r.addresses.length&&!c.listeners.length&&!c.outbound.length&&!i.length;if(r.state==="down"&&a)s.push(r.name);else o.push(c)}return{hostname:e.hostname,environment:e.environment,everywhere:t.filter((r)=>r.reach.scope==="everywhere"),interfaces:o,idle:s,dns:e.dns.map((r)=>({server:r,route:j(e,r)})),hidden:e.hidden}}var Se=(e)=>e.sockets.filter((t)=>t.pid===void 0&&t.uid!==e.uid).length;import{readFileSync as $t}from"node:fs";import{hostname as xt}from"node:os";import{join as B}from"node:path";import{spawnSync as ct}from"node:child_process";function at(e){let t;try{t=JSON.parse(e)}catch{return[]}return t.map((n)=>{let o=[];for(let[s,r]of Object.entries(n.NetworkSettings?.Ports??{})){let[i="0",c="tcp"]=s.split("/");for(let a of r??[])o.push({proto:c,hostIp:a.HostIp||"0.0.0.0",hostPort:Number(a.HostPort),containerPort:Number(i)})}return{id:(n.Id??"").slice(0,12),name:(n.Name??"").replace(/^\//,""),image:n.Config?.Image??"",project:n.Config?.Labels?.["com.docker.compose.project"],networks:Object.entries(n.NetworkSettings?.Networks??{}).map(([s,r])=>({name:s,bridge:s==="bridge"?"docker0":r.NetworkID&&!["host","none"].includes(s)?`br-${r.NetworkID.slice(0,12)}`:void 0,ip:r.IPAddress||r.GlobalIPv6Address||void 0})),ports:o}})}function ve(){let e=(o)=>ct("docker",o,{encoding:"utf8",timeout:5000}),t=e(["ps","-q"]);if(t.status!==0||!t.stdout.trim())return[];let n=e(["inspect",...t.stdout.trim().split(/\s+/)]);return n.status===0?at(n.stdout):[]}import{existsSync as L,readdirSync as te,readFileSync as dt,readlinkSync as ut}from"node:fs";import{networkInterfaces as pt}from"node:os";import{basename as Ie,join as b}from"node:path";function Re(){let e={};for(let[t,n]of Object.entries(pt()))e[t]=(n??[]).map((o)=>({family:o.family==="IPv6"?6:4,address:o.address.replace(/%.*$/,""),prefix:Number(o.cidr?.split("/")[1]??(o.family==="IPv6"?128:32))}));return e}function Pe(e,t){let n=b(e,"class/net"),o=[];try{o=te(n)}catch{}let s=o.map((r)=>{let i=b(n,r),c=(m)=>{try{return dt(b(i,m),"utf8").trim()}catch{return""}},a=(m)=>Number(c(m))||0,d;try{d=Ie(ut(b(i,"master")))}catch{}let u=[];try{u=te(b(i,"brif"))}catch{u=te(i).filter((m)=>m.startsWith("lower_")).map((m)=>m.slice(6))}let p=c("operstate"),f=c("address");return{name:r,index:a("ifindex"),kind:lt(i,c),state:p==="up"?"up":p==="down"?"down":"unknown",mtu:a("mtu"),mac:f&&f!=="00:00:00:00:00:00"?f:void 0,addresses:t[r]??[],master:d,members:u.sort(),rxBytes:a("statistics/rx_bytes"),txBytes:a("statistics/tx_bytes")}});for(let r of s)if(r.kind==="loopback"&&r.state==="unknown"&&r.addresses.length)r.state="up";return s.sort((r,i)=>r.index-i.index)}function lt(e,t){let n=t("type");if(n==="772")return"loopback";let o=t("uevent").match(/^DEVTYPE=(.*)$/m)?.[1];if(o==="wlan"||L(b(e,"wireless")))return"wifi";if(o==="bridge"||L(b(e,"bridge")))return"bridge";if(o==="vlan")return"vlan";if(o==="bond"||L(b(e,"bonding")))return"bond";if(o==="wireguard"||n==="65534")return o==="wireguard"?"wireguard":"tun";if(Ie(e).startsWith("veth"))return"veth";if(L(b(e,"device")))return"ethernet";return"virtual"}import{readdirSync as Te,readFileSync as Ne,readlinkSync as ft}from"node:fs";import{join as A}from"node:path";function Ee(e){let t=[],n=new Map,o=0,s=[];try{s=Te(e).filter((r)=>/^\d+$/.test(r))}catch{}s.sort((r,i)=>Number(r)-Number(i));for(let r of s){let i=Number(r),c=A(e,r),a;try{a=Te(A(c,"fd"))}catch{o++;continue}let d=0;for(let u of a){let p;try{p=ft(A(c,"fd",u))}catch{continue}let f=p.match(/^socket:\[(\d+)\]$/);if(!f)continue;d++;let m=Number(f[1]);if(!n.has(m))n.set(m,i)}if(d===0)continue;t.push(mt(c,i))}return{processes:t,owners:n,hidden:o}}function mt(e,t){let n=(d)=>{try{return Ne(A(e,d),"utf8")}catch{return""}},o=n("comm").trim(),s=n("cmdline").split("\x00").filter(Boolean),r=s[0]?.split("/").pop(),i=(o&&s.join(" ").includes(o)?o:r||o)||String(t),c=s.join(" ")||`[${i}]`,a=Number(n("status").match(/^Uid:\s+(\d+)/m)?.[1]??-1);return{pid:t,name:i,cmd:c,uid:a}}function Ce(e){let t={},n="";try{n=Ne(e,"utf8")}catch{}for(let o of n.split(`
3
- `)){let[s,,r]=o.split(":");if(s&&r&&/^\d+$/.test(r))t[Number(r)]=s}return t}import{readFileSync as ht}from"node:fs";import{join as gt}from"node:path";var Oe=1,je=2,Le=512;function bt(e){let t=[];for(let n of e.split(`
4
- `).slice(1)){let[o,s,r,i,,,c,a]=n.trim().split(/\s+/);if(!o||!s||!r||!i||!a)continue;let d=Number.parseInt(i,16);if(!(d&Oe)||d&Le)continue;t.push({family:4,dest:S(s),prefix:we(a),gateway:d&je?S(r):void 0,iface:o,metric:Number(c)||0})}return t}function kt(e){let t=[];for(let n of e.split(`
5
- `)){let o=n.trim().split(/\s+/);if(o.length<10)continue;let[s="",r="0",,,i="",c="0",,,a="0",d=""]=o,u=Number.parseInt(a,16);if(!(u&Oe)||u&Le)continue;let p=q(s),f=Number.parseInt(r,16);if(p.startsWith("ff")||f===128&&d!=="lo"||d==="lo"&&p!=="::1")continue;let m=q(i);t.push({family:6,dest:p,prefix:f,gateway:u&je&&m!=="::"?m:void 0,iface:d,metric:Number.parseInt(c,16)})}return t}function Ae(e){let t=(n)=>{try{return ht(gt(e,n),"utf8")}catch{return""}};return[...bt(t("net/route")),...kt(t("net/ipv6_route"))]}import{readFileSync as wt}from"node:fs";import{join as M}from"node:path";var yt={"01":"ESTABLISHED","02":"SYN_SENT","03":"SYN_RECV","04":"FIN_WAIT1","05":"FIN_WAIT2","06":"TIME_WAIT","07":"CLOSE","08":"CLOSE_WAIT","09":"LAST_ACK","0A":"LISTEN","0B":"CLOSING"};function W(e,t,n){let o=[];for(let s of e.split(`
6
- `).slice(1)){let r=s.trim().split(/\s+/);if(r.length<10)continue;let[,i="",c="",a="",,,,d="0",,u="0"]=r,p=(T)=>{let[l="",h="0"]=T.split(":");return{...n?ge(l):{family:4,address:S(l)},port:Number.parseInt(h,16)}},f=p(i),m=p(c),x=t==="udp"?m.port===0?"UNCONN":"ESTABLISHED":yt[a.toUpperCase()];if(!x)continue;o.push({proto:t,family:f.family,local:{address:f.address,port:f.port},remote:{address:m.address,port:m.port},state:x,uid:Number(d),inode:Number(u)})}return o}var _=(e)=>{try{return wt(e,"utf8")}catch{return""}};function Me(e){return[...W(_(M(e,"net/tcp")),"tcp",!1),...W(_(M(e,"net/tcp6")),"tcp",!0),...W(_(M(e,"net/udp")),"udp",!1),...W(_(M(e,"net/udp6")),"udp",!0)]}function We(e={}){let t=e.root??"/",n=B(t,"proc"),o=(d)=>{try{return $t(B(t,d),"utf8")}catch{return""}},s=Pe(B(t,"sys"),e.addresses??Re()),r=Ee(n),i=Me(n).map((d)=>({...d,pid:r.owners.get(d.inode)})),c=new Set(s.map((d)=>d.name)),a=(e.containers??ve()).map((d)=>({...d,networks:d.networks.map((u)=>({...u,bridge:u.bridge&&c.has(u.bridge)?u.bridge:void 0}))}));return{hostname:e.hostname??xt(),environment:/microsoft|wsl/i.test(o("proc/sys/kernel/osrelease"))?"wsl":"linux",interfaces:s,routes:Ae(n),sockets:i,processes:r.processes,containers:a,users:Ce(B(t,"etc/passwd")),dns:[...o("etc/resolv.conf").matchAll(/^\s*nameserver\s+(\S+)/gm)].map((d)=>d[1]??""),uid:e.uid??process.getuid?.()??0,hidden:r.hidden}}function g(e){let t=(n)=>(o)=>e.color&&o?`\x1B[${n}m${o}\x1B[0m`:o;return{bold:t("1"),dim:t("2"),red:t("31"),green:t("32"),yellow:t("33"),blue:t("34"),magenta:t("35"),cyan:t("36")}}function ne(e,t){return t!==void 0&&t>0&&e.length>t?`${e.slice(0,Math.max(0,t-1))}…`:e}function _e(e,t,n){let o=e[0]?.map((s,r)=>Math.max(...e.map((i)=>(i[r]??"").length)))??[];return e.map((s)=>{let r=0;return s.map((i,c)=>{let d=c===s.length-1?ne(i,n===void 0?void 0:Math.max(10,n-r)):i.padEnd(o[c]??0);return r+=(o[c]??0)+2,(t[c]??((u)=>u))(d)}).join(" ").trimEnd()}).join(`
7
- `)}var oe=(e)=>`${e.name} ${e.pid}`,re=(e)=>e.process?oe(e.process):e.user?`${e.user}'s process`:"closing, no process";function H(e){if(e.container)return`container ${e.container.name}${e.published?` :${e.published.containerPort}`:""}`;if(e.process)return oe(e.process);return`${e.user}'s process (sudo to see it)`}function Fe(e,t){let n=t.wsl&&e.addresses.some((s)=>k(s)||s.startsWith("127.")),o=e.reach.scope==="local"?"this machine only":e.reach.scope==="everywhere"?"every interface":e.reach.interfaces.join(", ")||"no interface";return n?`${o} + Windows`:o}var St=(e)=>{let t=e.addresses;if(t.length===2&&t.includes("0.0.0.0")&&t.includes("::"))return"*";return t.join(", ")};function D(e,t){switch(e.kind){case"local":{let n=e.listener?H(e.listener):e.process?oe(e.process):e.user&&`${e.user}'s process`||"this machine";return`${t} ${n}`}case"container":return`${t} container ${e.container.name}`;case"remote":return t}}var se=(e)=>e.kind==="unreachable"?"no route":e.gateway?`via ${e.iface} → ${e.gateway}`:`via ${e.iface}`;function He(e,t){let n=g(t);if(!e.length)return n.dim("nothing is listening");let o=[["PORT","ADDRESS","REACHABLE FROM","CLIENTS","OWNER"],...e.map((a)=>[`${a.proto} ${a.port}`,St(a),Fe(a,t),a.clients.length?String(a.clients.length):"",H(a)])],s=(a)=>a.startsWith("this machine")?n.green(a):a.startsWith("every")?n.yellow(a):a,r=[n.bold,(a)=>a,s,n.cyan,(a)=>a],[i=""]=_e(o,r.map(()=>(a)=>a),t.width).split(`
8
- `),c=_e(o,r,t.width).split(`
9
- `).slice(1);return[n.dim(i),...c].join(`
10
- `)}var Be=(e)=>{let t=["B","kB","MB","GB","TB"],n=0,o=e;while(o>=1000&&n<t.length-1)o/=1000,n++;return`${n?o.toFixed(1):o} ${t[n]}`},vt=(e)=>e.addresses.map((t)=>`${t.address}/${t.prefix}`);function Je(e,t,n=""){let o=g(t),s=e.state==="up"?o.green("up"):e.state==="down"?o.red("down"):o.dim(e.state);return[o.bold(e.name),e.kind,s,vt(e).join(" "),n&&o.dim(n)].filter(Boolean).join(" ")}var Ve=(e)=>{let t=[...new Set(e.containers.flatMap((n)=>n.container.networks.filter((o)=>o.bridge===e.iface.name).map((o)=>o.name)))];return t.length?`docker network ${t.join(", ")}`:""},F=(e)=>e.filter((t)=>!(t.family===6&&t.dest==="fe80::")),ie=(e)=>(e.prefix===0?"default":`${e.dest}/${e.prefix}`)+(e.gateway?` via ${e.gateway}`:"");function Ge(e,t,n){let o=g(n),s=[];for(let r of e){let i=r.iface;s.push(Je(i,n,Ve(r)));let c=[];if(F(r.routes).length)c.push(["routes",F(r.routes).map(ie).join(", ")]);if(i.members.length){let u=i.members.filter((p)=>p.startsWith("veth")).length;c.push(["ports",u===i.members.length?`${u} veth`:i.members.join(", ")])}if(r.containers.length)c.push(["containers",r.containers.map((u)=>`${u.container.name}${u.ip?` ${u.ip}`:""}`).join(", ")]);let a=r.listeners.length;if(a)c.push(["listening",`${a} port${a>1?"s":""} bound here`]);if(r.outbound.length)c.push(["out",`${r.outbound.length} connection${r.outbound.length>1?"s":""}`]);let d=[i.mac,i.mtu?`mtu ${i.mtu}`:"",`↓ ${Be(i.rxBytes)} ↑ ${Be(i.txBytes)}`].filter(Boolean);c.push(["",d.join(" ")]);for(let[u,p]of c)s.push(` ${o.dim(u.padEnd(10))} ${ne(p,n.width&&n.width-13)}`);s.push("")}if(t.length)s.push(o.dim(`down, no address: ${t.join(", ")}`));return s.join(`
11
- `).trimEnd()}function Ue(e,t){let n=g(t);if(!e.length)return n.dim("no connections");let o=new Map;for(let r of e){let i=re(r);o.set(i,[...o.get(i)??[],r])}let s=[];for(let[r,i]of[...o].sort((c,a)=>a[1].length-c[1].length)){s.push(`${n.bold(r)} ${n.dim(`${i.length} connection${i.length>1?"s":""}`)}`);let c=new Map;for(let a of i){let d=a.direction==="in"?n.magenta("←"):n.cyan("→"),u=a.direction==="in"?`:${a.local.port} `:"",p=a.peer.kind==="remote"?n.dim(se(a.peer.route)):"",f=a.state==="ESTABLISHED"?"":n.yellow(a.state),m=[` ${u}${d} ${D(a.peer,I(a.remote))}`,p,f].filter(Boolean).join(" ");c.set(m,(c.get(m)??0)+1)}for(let[a,d]of c)s.push(d>1?`${a} ${n.dim(`×${d}`)}`:a)}return s.join(`
12
- `)}function Ke(e,t){let n=g(t),o=[];if(!e.listeners.length&&!e.outbound.length)return`nothing listens on port ${e.port}, and nothing here talks to it`;for(let s of e.listeners){o.push(`${n.bold(`${s.proto} ${s.port}`)} ${H(s)}`);let r=[];if(s.process)r.push(["process",`${s.process.cmd} ${n.dim(`(pid ${s.process.pid}, ${s.user})`)}`]);else if(!s.container)r.push(["process",n.yellow(`owned by ${s.user}: run with sudo to see which process`)]);if(s.container){let i=s.container;r.push(["container",`${i.name} ${n.dim(i.image)}${i.project?n.dim(` compose project ${i.project}`):""}`]);for(let c of i.networks)r.push(["",`→ ${c.ip??"?"}:${s.published?.containerPort??s.port} on ${c.name}${c.bridge?n.dim(` (${c.bridge})`):""}`])}if(r.push(["bound to",s.addresses.join(", ")]),r.push(["reachable",Fe(s,t)+(s.reach.scope==="everywhere"?n.dim(`: ${s.reach.interfaces.join(", ")}`):"")]),s.reach.scope==="everywhere")r.push(["",n.yellow("any machine that can reach one of those interfaces can connect")]);if(s.clients.length){r.push(["clients",`${s.clients.length}`]);for(let i of s.clients)r.push(["",`← ${D(i.peer,I(i.remote))}`])}else r.push(["clients",n.dim("none right now")]);for(let[i,c]of r)o.push(` ${n.dim(i.padEnd(10))} ${c}`);o.push("")}if(e.outbound.length){o.push(n.bold(`connections to port ${e.port} elsewhere`));for(let s of e.outbound){let r=re(s),i=s.peer.kind==="remote"?` ${n.dim(se(s.peer.route))}`:"";o.push(` ${r} → ${D(s.peer,I(s.remote))}${i}`)}}return o.join(`
13
- `).trimEnd()}function Ye(e,t,n){let o=g(n),s=t===e.dest?e.dest:`${t} (${e.dest})`,r=[];switch(e.kind){case"loopback":return`${o.bold(s)} is this machine, through the loopback ${o.dim("(lo)")}`;case"local":return`${o.bold(s)} is this machine: an address of ${e.iface}, delivered through the loopback`;case"unreachable":return`${o.bold(s)}: ${o.red("no route")}: packets to it are dropped`;case"container":r.push(["is",`container ${e.container?.name}, on this machine`]);break;case"network":r.push(["is",`on the network of ${e.iface}, reached directly`]);break;case"gateway":r.push(["is",`beyond ${e.gateway}, the gateway on ${e.iface}`])}if(r.push(["interface",e.iface??""]),e.route)r.push(["route",`${ie(e.route)}${e.route.metric?` metric ${e.route.metric}`:""}`]);if(e.source)r.push(["from",e.source]);return[o.bold(s),...r.map(([i,c])=>` ${o.dim(i.padEnd(10))} ${c}`)].join(`
14
- `)}function ze(e,t){let n=g(t),o=[];o.push(`${n.bold(e.hostname)}${e.environment==="wsl"?n.dim(" (WSL: Windows reaches ports on 127.0.0.1 and every interface through its localhost)"):""}`),o.push("");let s=(r)=>`${n.bold(`${r.proto} ${r.port}`.padEnd(9))} ${H(r)}${r.clients.length?n.cyan(` ← ${r.clients.length} client${r.clients.length>1?"s":""}`):""}`;if(e.everywhere.length){o.push(`${n.yellow("listening on every interface")}`);for(let r of e.everywhere)o.push(` ${s(r)}`);o.push("")}for(let r of e.interfaces){let i=r.iface,c=Ve(r);if(o.push(Je(i,t,c)),F(r.routes).length)o.push(` ${n.dim("routes")} ${F(r.routes).map(ie).join(", ")}`);for(let a of r.listeners)o.push(` ${n.green("listen")} ${s(a)}`);for(let a of r.containers)o.push(` ${n.blue("holds")} ${a.container.name}${a.ip?` ${a.ip}`:""}${It(a.container)}`);if(i.members.length)o.push(n.dim(` ${i.members.length} port${i.members.length>1?"s":""}: ${i.members.join(" ")}`).slice(0,t.width??1/0));o.push(...De(r.outbound,"out",t)),o.push(...De(r.local,"local",t)),o.push("")}if(e.idle.length)o.push(n.dim(`down, no address: ${e.idle.join(", ")}`));if(e.dns.length)o.push(`${n.dim("dns")} ${e.dns.map((r)=>`${r.server} ${n.dim(r.route.kind==="loopback"||r.route.kind==="local"?"(local resolver)":se(r.route))}`).join(", ")}`);if(e.hidden)o.push(n.dim(`${e.hidden} processes of other users hidden: run with sudo to see their sockets`));return o.join(`
15
- `).trimEnd()}function It(e){let t=e.ports.filter((n,o,s)=>s.findIndex((r)=>r.hostPort===n.hostPort&&r.proto===n.proto)===o);return t.length?` ← host ${t.map((n)=>`:${n.hostPort}${n.hostPort!==n.containerPort?`→${n.containerPort}`:""}`).join(" ")}`:""}function De(e,t,n){let o=g(n),s=new Map;for(let i of e){let c=re(i),a=t==="local"?D(i.peer,`:${i.remote.port}`):I(i.remote),d=s.get(c)??new Map;d.set(a,(d.get(a)??0)+1),s.set(c,d)}let r=t==="out"?o.cyan("out"):o.magenta("local");return[...s].map(([i,c])=>{let a=[...c].map(([d,u])=>u>1?`${d} ×${u}`:d).join(", ");return` ${r}${t==="out"?" ":" "}${ne(`${i} → ${a}`,n.width&&n.width-10)}`})}var ce=(e)=>e&&{pid:e.pid,name:e.name,cmd:e.cmd},J=(e)=>e&&{id:e.id,name:e.name,image:e.image,project:e.project};function Rt(e){switch(e.kind){case"local":return{kind:e.kind,process:ce(e.process),user:e.user,listener:e.listener&&{proto:e.listener.proto,port:e.listener.port,container:e.listener.container?.name}};case"container":return{kind:e.kind,container:J(e.container),network:e.network};case"remote":return{kind:e.kind,route:V(e.route)}}}var V=(e)=>({...e,container:J(e.container)}),w=(e)=>({...e,process:ce(e.process),peer:Rt(e.peer)}),G=(e)=>({proto:e.proto,port:e.port,addresses:e.addresses,reach:e.reach,process:ce(e.process),user:e.user,container:J(e.container),published:e.published,clients:e.clients.map(w)}),ae=(e)=>({...e.iface,routes:e.routes,listeners:e.listeners.map((t)=>({proto:t.proto,port:t.port})),outbound:e.outbound.map(w),local:e.local.map(w),containers:e.containers.map((t)=>({...J(t.container),ip:t.ip}))}),qe=(e)=>({...e,everywhere:e.everywhere.map(G),interfaces:e.interfaces.map(ae),dns:e.dns.map((t)=>({server:t.server,route:V(t.route)}))});var Pt={json:["--json","machine-readable output"],tcp:["--tcp","TCP only"],udp:["--udp","UDP only"],all:["-a, --all","also connections that are opening or closing (TIME_WAIT…)"],"no-docker":["--no-docker","don't ask docker which containers publish ports"],"show-secrets":["--show-secrets",`don't mask the values of --token=, --password and the like
16
- in command lines`],help:["-h, --help","show help: netbeagle COMMAND --help for one command"],version:["-v, --version","show the version"]},U={list:{usage:["[list]"],summary:"every listening port: its process or container, and who can reach it",about:`Lists every port something listens on, with the process (or the docker container)
2
+ import{lookup as an}from"node:dns/promises";import{isIP as dn}from"node:net";import{parseArgs as un}from"node:util";var O={name:"netbeagle",version:"0.2.0",description:"See which process listens on each port, which interfaces reach it, and how everything on your machine connects",keywords:["network","ports","netstat","ss","lsof","interfaces","docker","routes","linux","macos","windows"],license:"MIT",author:"Michel Salib",repository:{type:"git",url:"git+https://github.com/michelsalib/netbeagle.git"},homepage:"https://github.com/michelsalib/netbeagle#readme",bugs:"https://github.com/michelsalib/netbeagle/issues",type:"module",bin:{netbeagle:"dist/netbeagle.js"},files:["dist/netbeagle.js"],engines:{node:">=20"},scripts:{dev:"bun src/cli.ts",build:"bun build src/cli.ts --target=node --minify --outfile dist/netbeagle.js","build:bin":"bun scripts/build-binaries.ts",test:"bun test --timeout 30000",typecheck:"tsc",lint:"biome check",prepublishOnly:"bun run typecheck && bun run lint && bun run build && bun test --timeout 30000"},devDependencies:{"@biomejs/biome":"2.5.15","@types/bun":"^1.4.2","@types/node":"^26.6.4",typescript:"^7.0.2"}};import{isIPv4 as be,isIPv6 as ke}from"node:net";function z(e){if(be(e))return e.split(".").map(Number);if(!ke(e))return;let t=e.replace(/%.*$/,""),o=t.match(/(\d+\.\d+\.\d+\.\d+)$/);if(o?.[1]){let[a,u,p,d]=o[1].split(".").map(Number);t=`${t.slice(0,-o[1].length)}${(a<<8|u).toString(16)}:${(p<<8|d).toString(16)}`}let[n="",s]=t.split("::"),r=n?n.split(":"):[],c=s?s.split(":"):[];return(s===void 0?r:[...r,...Array(8-r.length-c.length).fill("0"),...c]).flatMap((a)=>{let u=Number.parseInt(a,16);return[u>>8,u&255]})}var we=(e)=>be(e)?4:ke(e)?6:void 0;function X(e){let t=[];for(let r=0;r<16;r+=2)t.push((e[r]??0)<<8|(e[r+1]??0));let o=-1,n=1;for(let r=0;r<8;){if(t[r]!==0){r++;continue}let c=r;while(c<8&&t[c]===0)c++;if(c-r>n)o=r,n=c-r;r=c}let s=t.map((r)=>r.toString(16));if(o<0)return s.join(":");return`${s.slice(0,o).join(":")}::${s.slice(o+n).join(":")}`}function Q(e){let t=Number.parseInt(e,16);return[t&255,t>>>8&255,t>>>16&255,t>>>24&255]}var P=(e)=>Q(e).join(".");function $e(e){let t=[];for(let o=0;o<32;o+=8)t.push(...Q(e.slice(o,o+8)));if(t.slice(0,10).every((o)=>o===0)&&t[10]===255&&t[11]===255)return{family:4,address:t.slice(12).join(".")};return{family:6,address:X(t)}}function Z(e){let t=[];for(let o=0;o<32;o+=2)t.push(Number.parseInt(e.slice(o,o+2),16));return X(t)}function ee(e,t,o){let n=z(e),s=z(t);if(!n||!s||n.length!==s.length)return!1;for(let r=0;r<o;r++){let c=r>>3,i=128>>(r&7);if(((n[c]??0)&i)!==((s[c]??0)&i))return!1}return!0}function ye(e){let o=(z(e.address)??[]).map((n,s)=>{let r=Math.max(0,Math.min(8,e.prefix-s*8));return n&(255<<8-r&255)});return e.family===4?o.join("."):X(o)}function b(e){let t=e.replace(/%.*$/,"").toLowerCase(),o=t.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/);if(o?.[1])return{family:4,address:o[1]};return{family:t.includes(":")?6:4,address:t}}var S=(e)=>e==="0.0.0.0"||e==="::",N=(e)=>e==="::1"||e.startsWith("127."),xe=(e)=>e.startsWith("169.254.")||/^fe[89ab][0-9a-f]:/i.test(e),T=(e)=>e.address.includes(":")?`[${e.address}]:${e.port}`:`${e.address}:${e.port}`,Se=(e)=>Q(e).reduce((t,o)=>t+o.toString(2).replaceAll("0","").length,0);var ne=(e,t)=>t===void 0?void 0:e.users[t]??String(t),te=(e)=>e.interfaces.find((t)=>t.kind==="loopback")?.name??"lo",oe=(e,t)=>t===void 0?void 0:e.processes.find((o)=>o.pid===t),L=(e,t)=>e.interfaces.find((o)=>o.addresses.some((n)=>n.address===t));function Ie(e,t){for(let o of e.containers)for(let n of o.networks)if(n.ip===t)return{container:o,network:n};return}function A(e,t){let o=we(t)??4,n={dest:t,family:o};if(N(t))return{...n,kind:"loopback",iface:te(e),source:t};let s=L(e,t);if(s)return{...n,kind:"local",iface:s.name,source:t};let r=e.routes.filter((d)=>d.family===o&&ee(t,d.dest,d.prefix)).sort((d,l)=>l.prefix-d.prefix||d.metric-l.metric)[0];if(!r)return{...n,kind:"unreachable"};let c=e.interfaces.find((d)=>d.name===r.iface),i=r.gateway??t,a=c?.addresses.filter((d)=>d.family===o)??[],u=a.find((d)=>ee(i,ye(d),d.prefix))?.address??a.find((d)=>!xe(d.address))?.address??a[0]?.address,p=Ie(e,t);return{...n,kind:p?"container":r.gateway?"gateway":"network",iface:r.iface,gateway:r.gateway,route:r,source:u,container:p?.container}}function ht(e,t,o=[]){let n=(c)=>N(c)||L(e,c)?.kind==="loopback";if(t.every(n))return{scope:"local",interfaces:[te(e)]};let s=e.interfaces.filter((c)=>c.state!=="down"&&c.addresses.length);if(t.some(S)){let c=new Set(t.includes("0.0.0.0")?[4]:[]);if(t.includes("::")){c.add(6);let i=o.filter((a)=>a.local.address==="::");if(!i.length||i.some((a)=>!a.v6only))c.add(4)}return{scope:"everywhere",...!c.has(4)&&{ipv6Only:!0},interfaces:s.filter((i)=>!i.master).filter((i)=>i.addresses.some((a)=>c.has(a.family))).map((i)=>i.name)}}let r=new Set;for(let c of t){if(N(c))r.add(te(e));let i=L(e,c);if(i)r.add(i.name)}return{scope:"interfaces",interfaces:[...r]}}var gt=(e,t,o,n)=>e.proto===t&&e.hostPort===o&&(n===void 0||S(e.hostIp)||S(n)||e.hostIp===n);function bt(e,t,o,n){for(let s of e.containers)for(let r of s.ports)if(gt(r,t,o,n))return{container:s,published:r};return}var ve=(e)=>e.state==="LISTEN"||e.state==="UNCONN"&&e.local.port!==0;function R(e){let t=new Map;for(let n of e.sockets.filter(ve)){let s=bt(e,n.proto,n.local.port,n.local.address),r=s?`c${s.container.id}`:n.pid!==void 0?`p${n.pid}`:`u${n.uid??"?"}`,c=`${n.proto}/${n.local.port}/${r}`,i=t.get(c);if(!i)i={proto:n.proto,port:n.local.port,addresses:[],reach:{scope:"local",interfaces:[]},process:oe(e,n.pid),user:ne(e,n.uid),container:s?.container,published:s?.published,sockets:[],clients:[]},t.set(c,i);if(i.sockets.push(n),!i.addresses.includes(n.local.address))i.addresses.push(n.local.address)}for(let n of e.containers)for(let s of n.ports){if([...t.values()].some((a)=>a.proto===s.proto&&a.port===s.hostPort&&a.container?.id===n.id))continue;let c=`${s.proto}/${s.hostPort}/c${n.id}`,i=t.get(c);if(i){if(!i.addresses.includes(s.hostIp))i.addresses.push(s.hostIp);continue}t.set(c,{proto:s.proto,port:s.hostPort,addresses:[s.hostIp],reach:{scope:"local",interfaces:[]},user:"root",container:n,published:s,sockets:[],clients:[]})}let o=[...t.values()];for(let n of o)n.reach=ht(e,n.addresses,n.sockets);for(let n of E(e,o))if(n.direction==="in")C(o,n.proto,n.local)?.clients.push(n);return o.sort((n,s)=>n.port-s.port||n.proto.localeCompare(s.proto))}function C(e,t,o){return e.find((n)=>n.proto===t&&n.port===o.port&&n.addresses.some((s)=>s===o.address||S(s)))}function E(e,t){let o=t??R(e),n=[];for(let s of e.sockets){if(ve(s)||s.remote.port===0)continue;let r=C(o,s.proto,s.local)?"in":"out";n.push({proto:s.proto,local:s.local,remote:s.remote,state:s.state,process:oe(e,s.pid),user:s.orphan?void 0:ne(e,s.uid),direction:r,peer:kt(e,o,s)})}return n}function kt(e,t,o){let n=o.remote.address;if(N(n)||L(e,n)){let r=e.sockets.find((i)=>i!==o&&i.proto===o.proto&&i.local.port===o.remote.port&&i.remote.port===o.local.port&&i.local.address===n&&i.remote.address===o.local.address),c=C(t,o.proto,o.remote);return{kind:"local",process:oe(e,r?.pid),user:r?ne(e,r.uid):void 0,listener:c&&C(t,o.proto,o.local)!==c?c:void 0}}let s=Ie(e,n);if(s)return{kind:"container",container:s.container,network:s.network.name};return{kind:"remote",route:A(e,n)}}function Pe(e,t){return{port:t,listeners:R(e).filter((o)=>o.port===t),outbound:E(e).filter((o)=>o.direction==="out"&&o.remote.port===t&&!(o.peer.kind==="local"&&o.peer.listener?.port===t))}}function re(e){let t=R(e),o=E(e,t).filter((r)=>r.state==="ESTABLISHED"),n=[],s=[];for(let r of e.interfaces){if(r.master)continue;let c=e.containers.flatMap((u)=>u.networks.filter((p)=>p.bridge===r.name).map((p)=>({container:u,ip:p.ip}))),i={iface:r,routes:e.routes.filter((u)=>u.iface===r.name),listeners:t.filter((u)=>u.reach.scope!=="everywhere"&&u.reach.interfaces.includes(r.name)),outbound:o.filter((u)=>u.direction==="out"&&u.peer.kind==="remote"&&u.peer.route.iface===r.name),local:r.kind==="loopback"?o.filter((u)=>u.peer.kind==="local"&&u.direction==="out"):[],containers:c},a=!r.addresses.length&&!i.listeners.length&&!i.outbound.length&&!c.length;if(r.state==="down"&&a)s.push(r.name);else n.push(i)}return{hostname:e.hostname,environment:e.environment,everywhere:t.filter((r)=>r.reach.scope==="everywhere"),interfaces:n,idle:s,dns:e.dns.map((r)=>({server:r,route:A(e,r)})),hidden:e.hidden}}var Ne=(e)=>e.sockets.filter((t)=>t.pid===void 0&&!t.orphan&&t.uid!==e.uid).length;import{hostname as en}from"node:os";import{basename as wt}from"node:path";var $t={LISTEN:"LISTEN",ESTABLISHED:"ESTABLISHED",SYN_SENT:"SYN_SENT",SYN_RCVD:"SYN_RECV",FIN_WAIT_1:"FIN_WAIT1",FIN_WAIT_2:"FIN_WAIT2",TIME_WAIT:"TIME_WAIT",CLOSE_WAIT:"CLOSE_WAIT",LAST_ACK:"LAST_ACK",CLOSING:"CLOSING",CLOSED:"CLOSE"};function Te(e,t){let o=e.lastIndexOf("."),n=e.slice(0,o),s=e.slice(o+1);return{address:n==="*"?t===4?"0.0.0.0":"::":b(n).address,port:s==="*"?0:Number(s)}}function yt(e){let t=[];for(let o of e.split(`
3
+ `)){let n=o.trim().split(/\s+/),s=n[0]?.match(/^(tcp|udp)(4|6|46)$/);if(!s||n.length<5)continue;let r=s[1],c=s[2]==="46",i=s[2]==="4"?4:6,a=Te(n[3]??"",i),u=Te(n[4]??"",i),p=r==="udp"?u.port===0?"UNCONN":"ESTABLISHED":$t[n[5]??""];if(!p)continue;let d=b(a.address);t.push({proto:r,family:d.family,local:{address:d.address,port:a.port},remote:u,state:p,...i===6&&!c&&a.address==="::"&&{v6only:!0}})}return t}function Re(e,t){let o=e.match(/^\[?(.*?)\]?:(\d+|\*)$/),n=o?.[1]??"*";return{address:n==="*"?t?"::":"0.0.0.0":b(n).address,port:o?.[2]==="*"?0:Number(o?.[2]??0)}}function xt(e){let t=[],o=0,n=-1,s={};for(let r of e.split(`
4
+ `)){let c=r[0],i=r.slice(1);if(c==="p")o=Number(i);else if(c==="u")n=Number(i);else if(c==="f")s={};else if(c==="t")s.type=i;else if(c==="P")s.proto=i.toLowerCase();else if(c==="n"&&(s.proto==="tcp"||s.proto==="udp")){let a=s.type==="IPv6",[u="",p]=i.split("->");t.push({pid:o,uid:n,proto:s.proto,local:Re(u,a),remote:p?Re(p,a):void 0})}}return t}var Ee=(e,t,o)=>`${e} ${t.address} ${t.port} ${o?.port?`${o.address} ${o.port}`:"*"}`;function St(e,t){let o=new Map;for(let n of t){let s=Ee(n.proto,n.local,n.remote);if(!o.has(s))o.set(s,n)}return e.map((n)=>{let s=o.get(Ee(n.proto,n.local,n.remote));if(s)return{...n,pid:s.pid,uid:s.uid};return n.state==="TIME_WAIT"?{...n,orphan:!0}:n})}function It(e,t){let o=new Map;for(let r of t.split(`
5
+ `)){let c=r.match(/^\s*(\d+)\s+(.*)$/);if(c?.[1]&&c[2])o.set(Number(c[1]),c[2].trim())}let n=[],s={};for(let r of e.split(`
6
+ `)){let c=r.match(/^\s*(\d+)\s+(\d+)\s+(\S+)\s+(.*)$/);if(!c?.[1]||!c[2]||!c[3]||!c[4])continue;let i=Number(c[1]),a=Number(c[2]);s[a]=c[3];let u=wt(c[4].trim());n.push({pid:i,name:u,cmd:o.get(i)||u,uid:a})}return{processes:n,users:s}}var vt=(e)=>Number.parseInt(e,16).toString(2).replace(/0+$/,"").length;function Pt(e,t){if(/^lo\d/.test(e))return"loopback";if(t.has(e))return"wifi";if(/^bridge\d/.test(e))return"bridge";if(/^(utun|ipsec|ppp|tun|tap)\d/.test(e))return"tun";if(/^vlan\d/.test(e))return"vlan";if(/^bond\d/.test(e))return"bond";if(/^en\d/.test(e))return"ethernet";return"virtual"}function Nt(e,t){let o=new Set([...t.matchAll(/Hardware Port: Wi-Fi\s*\nDevice: (\S+)/g)].map((r)=>r[1]??"")),n=[],s;for(let r of e.split(`
7
+ `)){let c=r.match(/^(\S+): flags=\w+<([^>]*)> mtu (\d+)/);if(c?.[1]){s={name:c[1],index:0,kind:Pt(c[1],o),state:"unknown",mtu:Number(c[3]),addresses:[],members:[],rxBytes:0,txBytes:0,flags:(c[2]??"").split(",")},n.push(s);continue}if(!s)continue;let i=r.trim().split(/\s+/);if(i[0]==="ether")s.mac=i[1];else if(i[0]==="inet"&&i[1]&&i.includes("netmask"))s.addresses.push({family:4,address:i[1],prefix:vt(i[i.indexOf("netmask")+1]??"0")});else if(i[0]==="inet6"&&i[1]){let a=Number(i[i.indexOf("prefixlen")+1]??128);s.addresses.push({family:6,address:b(i[1]).address,prefix:a})}else if(i[0]==="status:")s.status=i[1];else if(i[0]==="member:"&&i[1])s.members.push(i[1])}for(let r of n){let c=r.flags.includes("UP")&&r.flags.includes("RUNNING");r.state=r.status?r.status==="active"&&c?"up":"down":c?"up":"down";for(let i of r.members){let a=n.find((u)=>u.name===i);if(a)a.master=r.name}}return n.map(({status:r,flags:c,...i})=>i)}function Tt(e){let t=new Map;for(let o of e.split(`
8
+ `)){let n=o.trim().split(/\s+/),s=n[2]?.match(/^<Link#(\d+)>$/);if(!s?.[1]||!n[0]||t.has(n[0]))continue;let r=(c)=>Number(n[n.length-c])||0;t.set(n[0].replace(/\*$/,""),{index:Number(s[1]),rx:r(5),tx:r(2)})}return t}function Rt(e,t,o){if(e==="default")return{dest:t===4?"0.0.0.0":"::",prefix:0};let[n="",s]=e.split("/");if(t===6)return{dest:b(n).address,prefix:s?Number(s):128};let r=n.split("."),c=[...r,"0","0","0"].slice(0,4).join("."),i=s?Number(s):o?32:r.length*8;return{dest:c,prefix:i}}function Et(e){let t=[],o,n={};for(let s of e.split(`
9
+ `)){if(/^Internet:/.test(s))o=4;else if(/^Internet6:/.test(s))o=6;let r=s.trim().split(/\s+/);if(r[0]==="Destination"){n=Object.fromEntries(r.map((m,g)=>[m,g]));continue}if(!o||n.Netif===void 0||r.length<=n.Netif)continue;let[c="",i="",a=""]=[r[0],r[1],r[n.Flags??2]],u=r[n.Netif]??"";if(/[ILRB]/.test(a)||/^(22[4-9]|23\d|255)\b|^ff/.test(c))continue;let{dest:p,prefix:d}=Rt(c,o,a.includes("H")),l=a.includes("G")?b(i).address:void 0;t.push({family:o,dest:p,prefix:d,gateway:l,iface:u,metric:0})}return t}function Oe(e){let t=Nt(e("ifconfig",["-a"]),e("networksetup",["-listallhardwareports"])),o=Tt(e("netstat",["-ibn"]));t.forEach((c,i)=>{let a=o.get(c.name);c.index=a?.index??i+1,c.rxBytes=a?.rx??0,c.txBytes=a?.tx??0});let n=St(yt(e("netstat",["-an"])),xt(e("lsof",["-nP","-iTCP","-iUDP","-FpuftPn"]))),{processes:s,users:r}=It(e("ps",["-axo","pid=,uid=,user=,comm="]),e("ps",["-axww","-o","pid=,args="]));return{environment:"macos",interfaces:t,routes:Et(e("netstat",["-rn"])),sockets:n,processes:s,users:r,dns:[...e("cat",["/etc/resolv.conf"]).matchAll(/^\s*nameserver\s+(\S+)/gm)].map((c)=>b(c[1]??"").address),hidden:0}}import{spawnSync as Ot}from"node:child_process";function Lt(e){let t;try{t=JSON.parse(e)}catch{return[]}return t.map((o)=>{let n=[];for(let[s,r]of Object.entries(o.NetworkSettings?.Ports??{})){let[c="0",i="tcp"]=s.split("/");for(let a of r??[])n.push({proto:i,hostIp:a.HostIp||"0.0.0.0",hostPort:Number(a.HostPort),containerPort:Number(c)})}return{id:(o.Id??"").slice(0,12),name:(o.Name??"").replace(/^\//,""),image:o.Config?.Image??"",project:o.Config?.Labels?.["com.docker.compose.project"],networks:Object.entries(o.NetworkSettings?.Networks??{}).map(([s,r])=>({name:s,bridge:s==="bridge"?"docker0":r.NetworkID&&!["host","none"].includes(s)?`br-${r.NetworkID.slice(0,12)}`:void 0,ip:r.IPAddress||r.GlobalIPv6Address||void 0})),ports:n}})}function Le(){let e=(n)=>Ot("docker",n,{encoding:"utf8",timeout:5000}),t=e(["ps","-q"]);if(t.status!==0||!t.stdout.trim())return[];let o=e(["inspect",...t.stdout.trim().split(/\s+/)]);return o.status===0?Lt(o.stdout):[]}import{readFileSync as Jt}from"node:fs";import{join as D}from"node:path";import{existsSync as j,readdirSync as se,readFileSync as Ct,readlinkSync as At}from"node:fs";import{networkInterfaces as jt}from"node:os";import{basename as Ce,join as x}from"node:path";function Ae(){let e={};for(let[t,o]of Object.entries(jt()))e[t]=(o??[]).map((n)=>({family:n.family==="IPv6"?6:4,address:n.address.replace(/%.*$/,""),prefix:Number(n.cidr?.split("/")[1]??(n.family==="IPv6"?128:32))}));return e}function je(e,t){let o=x(e,"class/net"),n=[];try{n=se(o)}catch{}let s=n.map((r)=>{let c=x(o,r),i=(m)=>{try{return Ct(x(c,m),"utf8").trim()}catch{return""}},a=(m)=>Number(i(m))||0,u;try{u=Ce(At(x(c,"master")))}catch{}let p=[];try{p=se(x(c,"brif"))}catch{p=se(c).filter((m)=>m.startsWith("lower_")).map((m)=>m.slice(6))}let d=i("operstate"),l=i("address");return{name:r,index:a("ifindex"),kind:_t(c,i),state:d==="up"?"up":d==="down"?"down":"unknown",mtu:a("mtu"),mac:l&&l!=="00:00:00:00:00:00"?l:void 0,addresses:t[r]??[],master:u,members:p.sort(),rxBytes:a("statistics/rx_bytes"),txBytes:a("statistics/tx_bytes")}});for(let r of s)if(r.kind==="loopback"&&r.state==="unknown"&&r.addresses.length)r.state="up";return s.sort((r,c)=>r.index-c.index)}function _t(e,t){let o=t("type");if(o==="772")return"loopback";let n=t("uevent").match(/^DEVTYPE=(.*)$/m)?.[1];if(n==="wlan"||j(x(e,"wireless")))return"wifi";if(n==="bridge"||j(x(e,"bridge")))return"bridge";if(n==="vlan")return"vlan";if(n==="bond"||j(x(e,"bonding")))return"bond";if(n==="wireguard"||o==="65534")return n==="wireguard"?"wireguard":"tun";if(Ce(e).startsWith("veth"))return"veth";if(j(x(e,"device")))return"ethernet";return"virtual"}import{readdirSync as _e,readFileSync as We,readlinkSync as Wt}from"node:fs";import{join as _}from"node:path";function Me(e){let t=[],o=new Map,n=0,s=[];try{s=_e(e).filter((r)=>/^\d+$/.test(r))}catch{}s.sort((r,c)=>Number(r)-Number(c));for(let r of s){let c=Number(r),i=_(e,r),a;try{a=_e(_(i,"fd"))}catch{n++;continue}let u=0;for(let p of a){let d;try{d=Wt(_(i,"fd",p))}catch{continue}let l=d.match(/^socket:\[(\d+)\]$/);if(!l)continue;u++;let m=Number(l[1]);if(!o.has(m))o.set(m,c)}if(u===0)continue;t.push(Mt(i,c))}return{processes:t,owners:o,hidden:n}}function Mt(e,t){let o=(u)=>{try{return We(_(e,u),"utf8")}catch{return""}},n=o("comm").trim(),s=o("cmdline").split("\x00").filter(Boolean),r=s[0]?.split("/").pop(),c=(n&&s.join(" ").includes(n)?n:r||n)||String(t),i=s.join(" ")||`[${c}]`,a=Number(o("status").match(/^Uid:\s+(\d+)/m)?.[1]??-1);return{pid:t,name:c,cmd:i,uid:a}}function Fe(e){let t={},o="";try{o=We(e,"utf8")}catch{}for(let n of o.split(`
10
+ `)){let[s,,r]=n.split(":");if(s&&r&&/^\d+$/.test(r))t[Number(r)]=s}return t}import{readFileSync as Ft}from"node:fs";import{join as Dt}from"node:path";var De=1,Be=2,Ge=512;function Bt(e){let t=[];for(let o of e.split(`
11
+ `).slice(1)){let[n,s,r,c,,,i,a]=o.trim().split(/\s+/);if(!n||!s||!r||!c||!a)continue;let u=Number.parseInt(c,16);if(!(u&De)||u&Ge)continue;t.push({family:4,dest:P(s),prefix:Se(a),gateway:u&Be?P(r):void 0,iface:n,metric:Number(i)||0})}return t}function Gt(e){let t=[];for(let o of e.split(`
12
+ `)){let n=o.trim().split(/\s+/);if(n.length<10)continue;let[s="",r="0",,,c="",i="0",,,a="0",u=""]=n,p=Number.parseInt(a,16);if(!(p&De)||p&Ge)continue;let d=Z(s),l=Number.parseInt(r,16);if(d.startsWith("ff")||l===128&&u!=="lo"||u==="lo"&&d!=="::1")continue;let m=Z(c);t.push({family:6,dest:d,prefix:l,gateway:p&Be&&m!=="::"?m:void 0,iface:u,metric:Number.parseInt(i,16)})}return t}function He(e){let t=(o)=>{try{return Ft(Dt(e,o),"utf8")}catch{return""}};return[...Bt(t("net/route")),...Gt(t("net/ipv6_route"))]}import{readFileSync as Ht}from"node:fs";import{join as W}from"node:path";var Ut={"01":"ESTABLISHED","02":"SYN_SENT","03":"SYN_RECV","04":"FIN_WAIT1","05":"FIN_WAIT2","06":"TIME_WAIT","07":"CLOSE","08":"CLOSE_WAIT","09":"LAST_ACK","0A":"LISTEN","0B":"CLOSING"};function M(e,t,o){let n=[];for(let s of e.split(`
13
+ `).slice(1)){let r=s.trim().split(/\s+/);if(r.length<10)continue;let[,c="",i="",a="",,,,u="0",,p="0"]=r,d=(w)=>{let[f="",h="0"]=w.split(":");return{...o?$e(f):{family:4,address:P(f)},port:Number.parseInt(h,16)}},l=d(c),m=d(i),g=t==="udp"?m.port===0?"UNCONN":"ESTABLISHED":Ut[a.toUpperCase()];if(!g)continue;n.push({proto:t,family:l.family,local:{address:l.address,port:l.port},remote:{address:m.address,port:m.port},state:g,uid:Number(u),inode:Number(p),...p==="0"&&{orphan:!0}})}return n}var F=(e)=>{try{return Ht(e,"utf8")}catch{return""}};function Ue(e){return[...M(F(W(e,"net/tcp")),"tcp",!1),...M(F(W(e,"net/tcp6")),"tcp",!0),...M(F(W(e,"net/udp")),"udp",!1),...M(F(W(e,"net/udp6")),"udp",!0)]}function Je(e,t){let o=D(e,"proc"),n=(i)=>{try{return Jt(D(e,i),"utf8")}catch{return""}},s=Me(o),r=n("proc/sys/net/ipv6/bindv6only").trim()==="1",c=Ue(o).map((i)=>({...i,pid:i.inode===void 0?void 0:s.owners.get(i.inode),...r&&i.local.address==="::"&&{v6only:!0}}));return{environment:/microsoft|wsl/i.test(n("proc/sys/kernel/osrelease"))?"wsl":"linux",interfaces:je(D(e,"sys"),t??Ae()),routes:He(o),sockets:c,processes:s.processes,users:Fe(D(e,"etc/passwd")),dns:[...n("etc/resolv.conf").matchAll(/^\s*nameserver\s+(\S+)/gm)].map((i)=>i[1]??""),hidden:s.hidden}}import{spawnSync as Kt}from"node:child_process";var Ke=(e,t)=>{let o=Kt(e,t,{encoding:"utf8",timeout:15000,maxBuffer:67108864,windowsHide:!0});return o.error?"":o.stdout??""};var Vt=`
14
+ $ProgressPreference = 'SilentlyContinue'
15
+ $ErrorActionPreference = 'SilentlyContinue'
16
+ [Console]::OutputEncoding = [Text.Encoding]::UTF8
17
+ $ifaces = foreach ($n in [System.Net.NetworkInformation.NetworkInterface]::GetAllNetworkInterfaces()) {
18
+ $p = $n.GetIPProperties()
19
+ $v4 = $null; try { $v4 = $p.GetIPv4Properties() } catch {}
20
+ $v6 = $null; try { $v6 = $p.GetIPv6Properties() } catch {}
21
+ $st = $null; try { $st = $n.GetIPStatistics() } catch {}
22
+ [ordered]@{
23
+ name = $n.Name; description = $n.Description; type = "$($n.NetworkInterfaceType)"
24
+ status = "$($n.OperationalStatus)"; mac = "$($n.GetPhysicalAddress())"
25
+ index = if ($v4) { $v4.Index } elseif ($v6) { $v6.Index } else { 0 }
26
+ mtu = if ($v4) { $v4.Mtu } elseif ($v6) { $v6.Mtu } else { 0 }
27
+ rx = if ($st) { $st.BytesReceived } else { 0 }; tx = if ($st) { $st.BytesSent } else { 0 }
28
+ addresses = @($p.UnicastAddresses | ForEach-Object { ,@("$($_.Address)", $_.PrefixLength) })
29
+ dns = @($p.DnsAddresses | ForEach-Object { "$_" })
30
+ }
31
+ }
32
+ $g = [System.Net.NetworkInformation.IPGlobalProperties]::GetIPGlobalProperties()
33
+ [ordered]@{
34
+ tcp4 = @($g.GetActiveTcpListeners() | Where-Object { "$($_.Address)" -eq '0.0.0.0' } | ForEach-Object { $_.Port })
35
+ udp4 = @($g.GetActiveUdpListeners() | Where-Object { "$($_.Address)" -eq '0.0.0.0' } | ForEach-Object { $_.Port })
36
+ tcp = @(Get-NetTCPConnection | ForEach-Object { ,@($_.LocalAddress, $_.LocalPort, $_.RemoteAddress, $_.RemotePort, "$($_.State)", $_.OwningProcess) })
37
+ udp = @(Get-NetUDPEndpoint | ForEach-Object { ,@($_.LocalAddress, $_.LocalPort, $_.OwningProcess) })
38
+ interfaces = @($ifaces)
39
+ routes = @(Get-NetRoute | ForEach-Object { ,@($_.DestinationPrefix, $_.NextHop, $_.InterfaceAlias, ($_.RouteMetric + $_.InterfaceMetric)) })
40
+ processes = @(Get-CimInstance Win32_Process -Property ProcessId, Name, CommandLine | ForEach-Object { ,@($_.ProcessId, $_.Name, $_.CommandLine) })
41
+ } | ConvertTo-Json -Depth 5 -Compress
42
+ `,Yt={Listen:"LISTEN",Established:"ESTABLISHED",SynSent:"SYN_SENT",SynReceived:"SYN_RECV",FinWait1:"FIN_WAIT1",FinWait2:"FIN_WAIT2",TimeWait:"TIME_WAIT",CloseWait:"CLOSE_WAIT",LastAck:"LAST_ACK",Closing:"CLOSING",Bound:"CLOSE",Closed:"CLOSE",DeleteTCB:"CLOSE"};function qt(e,t){if(e==="Loopback")return"loopback";if(/wireguard/i.test(t))return"wireguard";if(/tap-windows|wintun|openvpn|tailscale|vpn/i.test(t)||/Tunnel|Ppp/.test(e))return"tun";if(/virtual|hyper-v|vmware|bluetooth|wi-fi direct/i.test(t))return"virtual";if(e==="Wireless80211")return"wifi";if(/Ethernet/.test(e))return"ethernet";return"virtual"}var zt=(e)=>e.length===12?e.toLowerCase().replace(/(..)(?!$)/g,"$1:"):void 0,Xt=(e)=>e.startsWith("fec0:0:0:ffff::"),Qt=(e)=>/^(22[4-9]|23\d)\./.test(e)||e.startsWith("ff");function Zt(e){let t=(e.interfaces??[]).map((d)=>{let l=d.addresses.map(([m,g])=>({...b(m),prefix:g}));return{name:d.name,index:d.index,kind:qt(d.type,d.description),state:d.status==="Up"?"up":d.status==="Down"?"down":"unknown",mtu:d.mtu>0?d.mtu:0,mac:zt(d.mac),addresses:l,members:[],rxBytes:d.rx,txBytes:d.tx}}),o=new Set(t.flatMap((d)=>d.addresses.map((l)=>l.address))),n=[];for(let[d,l,m,g]of e.routes??[]){let[w="",f="0"]=d.split("/"),h=b(w),k=Number(f),ft=k===(h.family===4?32:128);if(Qt(h.address)||h.address==="255.255.255.255")continue;if(ft&&(o.has(h.address)||h.address.endsWith(".255")))continue;let q=b(l).address;n.push({family:h.family,dest:h.address,prefix:k,gateway:q==="0.0.0.0"||q==="::"?void 0:q,iface:m,metric:g})}let s=(d,l,m,g,w,f,h)=>{let k=b(l);return{proto:d,family:k.family,local:{address:k.address,port:m},remote:{address:b(g).address,port:w},state:f,...h===0?{orphan:!0}:{pid:h},...k.address==="::"&&!i(d,m)&&{v6only:!0}}},r=new Set([...(e.tcp??[]).filter((d)=>d[0]==="0.0.0.0").map((d)=>`tcp ${d[1]}`),...(e.udp??[]).filter((d)=>d[0]==="0.0.0.0").map((d)=>`udp ${d[1]}`)]),c=new Set([...(e.tcp4??[]).map((d)=>`tcp ${d}`),...(e.udp4??[]).map((d)=>`udp ${d}`)]),i=(d,l)=>c.has(`${d} ${l}`)&&!r.has(`${d} ${l}`),a=[];for(let[d,l,m,g,w,f]of e.tcp??[]){let h=Yt[w];if(h)a.push(s("tcp",d,l,m,g,h,f))}for(let[d,l,m]of e.udp??[])a.push(s("udp",d,l,d.includes(":")?"::":"0.0.0.0",0,"UNCONN",m));let u=(e.processes??[]).map(([d,l,m])=>{let g=l.replace(/\.exe$/i,"");return{pid:d,name:g,cmd:m?.trim()||g}}),p=[...new Set((e.interfaces??[]).filter((d)=>d.status==="Up"&&d.type!=="Loopback").flatMap((d)=>d.dns.map((l)=>b(l).address)).filter((d)=>!Xt(d)))];return{environment:"windows",interfaces:t,routes:n,sockets:a,processes:u,users:{},dns:p,hidden:0}}function Ve(e){let t=Buffer.from(Vt,"utf16le").toString("base64"),o=e("powershell.exe",["-NoProfile","-NonInteractive","-EncodedCommand",t]),n={};try{n=JSON.parse(o.replace(/^/,""))}catch{}return Zt(n)}function Ye(e={}){let t=e.platform??process.platform,o=e.run??Ke,n=t==="win32"?Ve(o):t==="darwin"?Oe(o):Je(e.root??"/",e.addresses),s=new Set(n.interfaces.map((c)=>c.name)),r=(e.containers??Le()).map((c)=>({...c,networks:c.networks.map((i)=>({...i,bridge:i.bridge&&s.has(i.bridge)?i.bridge:void 0}))}));return{hostname:e.hostname??en(),...n,containers:r,uid:e.uid??process.getuid?.()}}function y(e){let t=(o)=>(n)=>e.color&&n?`\x1B[${o}m${n}\x1B[0m`:n;return{bold:t("1"),dim:t("2"),red:t("31"),green:t("32"),yellow:t("33"),blue:t("34"),magenta:t("35"),cyan:t("36")}}function ie(e,t){return t!==void 0&&t>0&&e.length>t?`${e.slice(0,Math.max(0,t-1))}…`:e}function qe(e,t,o){let n=e[0]?.map((s,r)=>Math.max(...e.map((c)=>(c[r]??"").length)))??[];return e.map((s)=>{let r=0;return s.map((c,i)=>{let u=i===s.length-1?ie(c,o===void 0?void 0:Math.max(10,o-r)):c.padEnd(n[i]??0);return r+=(n[i]??0)+2,(t[i]??((p)=>p))(u)}).join(" ").trimEnd()}).join(`
43
+ `)}var ce=(e)=>`${e.name} ${e.pid}`,H=(e)=>e?.windows?"run as administrator":"run with sudo",ae=(e)=>e.process?ce(e.process):e.user?`${e.user}'s process`:"closing, no process";function U(e,t){if(e.container)return`container ${e.container.name}${e.published?` :${e.published.containerPort}`:""}`;if(e.process)return ce(e.process);let o=e.sockets.find((n)=>n.pid!==void 0)?.pid;if(o!==void 0)return`pid ${o}`;return`${e.user??"another user"}'s process (${t?.windows?"run as administrator":"sudo"} to see it)`}function Qe(e,t){let o=t.wsl&&e.addresses.some((s)=>S(s)||s.startsWith("127.")),n=e.reach.scope==="local"?"this machine only":e.reach.scope==="everywhere"?e.reach.ipv6Only?"every interface, IPv6 only":"every interface":e.reach.interfaces.join(", ")||"no interface";return o?`${n} + Windows`:n}var tn=(e)=>{let t=e.addresses;if(t.length===2&&t.includes("0.0.0.0")&&t.includes("::"))return"*";return t.length>2?`${t.slice(0,2).join(", ")} +${t.length-2}`:t.join(", ")};function B(e,t,o){switch(e.kind){case"local":{let n=e.listener?U(e.listener,o):e.process?ce(e.process):e.user&&`${e.user}'s process`||"this machine";return`${t} ${n}`}case"container":return`${t} container ${e.container.name}`;case"remote":return t}}var de=(e)=>e.kind==="unreachable"?"no route":e.gateway?`via ${e.iface} → ${e.gateway}`:`via ${e.iface}`;function Ze(e,t){let o=y(t);if(!e.length)return o.dim("nothing is listening");let n=[["PORT","ADDRESS","REACHABLE FROM","CLIENTS","OWNER"],...e.map((a)=>[`${a.proto} ${a.port}`,tn(a),Qe(a,t),a.clients.length?String(a.clients.length):"",U(a,t)])],s=(a)=>a.startsWith("this machine")?o.green(a):a.startsWith("every")?o.yellow(a):a,r=[o.bold,(a)=>a,s,o.cyan,(a)=>a],[c=""]=qe(n,r.map(()=>(a)=>a),t.width).split(`
44
+ `),i=qe(n,r,t.width).split(`
45
+ `).slice(1);return[o.dim(c),...i].join(`
46
+ `)}var ze=(e)=>{let t=["B","kB","MB","GB","TB"],o=0,n=e;while(n>=1000&&o<t.length-1)n/=1000,o++;return`${o?n.toFixed(1):n} ${t[o]}`},nn=(e)=>e.addresses.map((t)=>`${t.address}/${t.prefix}`);function et(e,t,o=""){let n=y(t),s=e.state==="up"?n.green("up"):e.state==="down"?n.red("down"):n.dim(e.state);return[n.bold(e.name),e.kind,s,nn(e).join(" "),o&&n.dim(o)].filter(Boolean).join(" ")}var tt=(e)=>{let t=[...new Set(e.containers.flatMap((o)=>o.container.networks.filter((n)=>n.bridge===e.iface.name).map((n)=>n.name)))];return t.length?`docker network ${t.join(", ")}`:""},G=(e)=>e.filter((t)=>!(t.family===6&&t.dest==="fe80::")),ue=(e)=>(e.prefix===0?"default":`${e.dest}/${e.prefix}`)+(e.gateway?` via ${e.gateway}`:"");function nt(e,t,o){let n=y(o),s=[];for(let r of e){let c=r.iface;s.push(et(c,o,tt(r)));let i=[];if(G(r.routes).length)i.push(["routes",G(r.routes).map(ue).join(", ")]);if(c.members.length){let p=c.members.filter((d)=>d.startsWith("veth")).length;i.push(["ports",p===c.members.length?`${p} veth`:c.members.join(", ")])}if(r.containers.length)i.push(["containers",r.containers.map((p)=>`${p.container.name}${p.ip?` ${p.ip}`:""}`).join(", ")]);let a=r.listeners.length;if(a)i.push(["listening",`${a} port${a>1?"s":""} bound here`]);if(r.outbound.length)i.push(["out",`${r.outbound.length} connection${r.outbound.length>1?"s":""}`]);let u=[c.mac,c.mtu?`mtu ${c.mtu}`:"",`↓ ${ze(c.rxBytes)} ↑ ${ze(c.txBytes)}`].filter(Boolean);i.push(["",u.join(" ")]);for(let[p,d]of i)s.push(` ${n.dim(p.padEnd(10))} ${ie(d,o.width&&o.width-13)}`);s.push("")}if(t.length)s.push(n.dim(`down, no address: ${t.join(", ")}`));return s.join(`
47
+ `).trimEnd()}function ot(e,t){let o=y(t);if(!e.length)return o.dim("no connections");let n=new Map;for(let r of e){let c=ae(r);n.set(c,[...n.get(c)??[],r])}let s=[];for(let[r,c]of[...n].sort((i,a)=>a[1].length-i[1].length)){s.push(`${o.bold(r)} ${o.dim(`${c.length} connection${c.length>1?"s":""}`)}`);let i=new Map;for(let a of c){let u=a.direction==="in"?o.magenta("←"):o.cyan("→"),p=a.direction==="in"?`:${a.local.port} `:"",d=a.peer.kind==="remote"?o.dim(de(a.peer.route)):"",l=a.state==="ESTABLISHED"?"":o.yellow(a.state),m=[` ${p}${u} ${B(a.peer,T(a.remote),t)}`,d,l].filter(Boolean).join(" ");i.set(m,(i.get(m)??0)+1)}for(let[a,u]of i)s.push(u>1?`${a} ${o.dim(`×${u}`)}`:a)}return s.join(`
48
+ `)}function rt(e,t){let o=y(t),n=[];if(!e.listeners.length&&!e.outbound.length)return`nothing listens on port ${e.port}, and nothing here talks to it`;for(let s of e.listeners){n.push(`${o.bold(`${s.proto} ${s.port}`)} ${U(s,t)}`);let r=[];if(s.process)r.push(["process",`${s.process.cmd} ${o.dim(`(pid ${s.process.pid}${s.user?`, ${s.user}`:""})`)}`]);else if(!s.container)r.push(["process",o.yellow(`owned by ${s.user??"another user"}: ${H(t)} to see which process`)]);if(s.container){let c=s.container;r.push(["container",`${c.name} ${o.dim(c.image)}${c.project?o.dim(` compose project ${c.project}`):""}`]);for(let i of c.networks)r.push(["",`→ ${i.ip??"?"}:${s.published?.containerPort??s.port} on ${i.name}${i.bridge?o.dim(` (${i.bridge})`):""}`])}if(r.push(["bound to",s.addresses.join(", ")]),r.push(["reachable",Qe(s,t)+(s.reach.scope==="everywhere"?o.dim(`: ${s.reach.interfaces.join(", ")}`):"")]),s.reach.scope==="everywhere")r.push(["",o.yellow("any machine that can reach one of those interfaces can connect")]);if(s.clients.length){r.push(["clients",`${s.clients.length}`]);for(let c of s.clients)r.push(["",`← ${B(c.peer,T(c.remote),t)}`])}else r.push(["clients",o.dim("none right now")]);for(let[c,i]of r)n.push(` ${o.dim(c.padEnd(10))} ${i}`);n.push("")}if(e.outbound.length){n.push(o.bold(`connections to port ${e.port} elsewhere`));for(let s of e.outbound){let r=ae(s),c=s.peer.kind==="remote"?` ${o.dim(de(s.peer.route))}`:"";n.push(` ${r} → ${B(s.peer,T(s.remote),t)}${c}`)}}return n.join(`
49
+ `).trimEnd()}function st(e,t,o){let n=y(o),s=t===e.dest?e.dest:`${t} (${e.dest})`,r=[];switch(e.kind){case"loopback":return`${n.bold(s)} is this machine, through the loopback ${n.dim("(lo)")}`;case"local":return`${n.bold(s)} is this machine: an address of ${e.iface}, delivered through the loopback`;case"unreachable":return`${n.bold(s)}: ${n.red("no route")}: packets to it are dropped`;case"container":r.push(["is",`container ${e.container?.name}, on this machine`]);break;case"network":r.push(["is",`on the network of ${e.iface}, reached directly`]);break;case"gateway":r.push(["is",`beyond ${e.gateway}, the gateway on ${e.iface}`])}if(r.push(["interface",e.iface??""]),e.route)r.push(["route",`${ue(e.route)}${e.route.metric?` metric ${e.route.metric}`:""}`]);if(e.source)r.push(["from",e.source]);return[n.bold(s),...r.map(([c,i])=>` ${n.dim(c.padEnd(10))} ${i}`)].join(`
50
+ `)}function it(e,t){let o=y(t),n=[];n.push(`${o.bold(e.hostname)}${e.environment==="wsl"?o.dim(" (WSL: Windows reaches ports on 127.0.0.1 and every interface through its localhost)"):""}`),n.push("");let s=(r)=>`${o.bold(`${r.proto} ${r.port}`.padEnd(9))} ${U(r,t)}${r.clients.length?o.cyan(` ← ${r.clients.length} client${r.clients.length>1?"s":""}`):""}`;if(e.everywhere.length){n.push(`${o.yellow("listening on every interface")}`);for(let r of e.everywhere)n.push(` ${s(r)}`);n.push("")}for(let r of e.interfaces){let c=r.iface,i=tt(r);if(n.push(et(c,t,i)),G(r.routes).length)n.push(` ${o.dim("routes")} ${G(r.routes).map(ue).join(", ")}`);for(let a of r.listeners)n.push(` ${o.green("listen")} ${s(a)}`);for(let a of r.containers)n.push(` ${o.blue("holds")} ${a.container.name}${a.ip?` ${a.ip}`:""}${on(a.container)}`);if(c.members.length)n.push(o.dim(` ${c.members.length} port${c.members.length>1?"s":""}: ${c.members.join(" ")}`).slice(0,t.width??1/0));n.push(...Xe(r.outbound,"out",t)),n.push(...Xe(r.local,"local",t)),n.push("")}if(e.idle.length)n.push(o.dim(`down, no address: ${e.idle.join(", ")}`));if(e.dns.length)n.push(`${o.dim("dns")} ${e.dns.map((r)=>`${r.server} ${o.dim(r.route.kind==="loopback"||r.route.kind==="local"?"(local resolver)":de(r.route))}`).join(", ")}`);if(e.hidden)n.push(o.dim(`${e.hidden} processes of other users hidden: ${H(t)} to see their sockets`));return n.join(`
51
+ `).trimEnd()}function on(e){let t=e.ports.filter((o,n,s)=>s.findIndex((r)=>r.hostPort===o.hostPort&&r.proto===o.proto)===n);return t.length?` ← host ${t.map((o)=>`:${o.hostPort}${o.hostPort!==o.containerPort?`→${o.containerPort}`:""}`).join(" ")}`:""}function Xe(e,t,o){let n=y(o),s=new Map;for(let c of e){let i=ae(c),a=t==="local"?B(c.peer,`:${c.remote.port}`,o):T(c.remote),u=s.get(i)??new Map;u.set(a,(u.get(a)??0)+1),s.set(i,u)}let r=t==="out"?n.cyan("out"):n.magenta("local");return[...s].map(([c,i])=>{let a=[...i].map(([u,p])=>p>1?`${u} ×${p}`:u).join(", ");return` ${r}${t==="out"?" ":" "}${ie(`${c} → ${a}`,o.width&&o.width-10)}`})}var pe=(e)=>e&&{pid:e.pid,name:e.name,cmd:e.cmd},J=(e)=>e&&{id:e.id,name:e.name,image:e.image,project:e.project};function rn(e){switch(e.kind){case"local":return{kind:e.kind,process:pe(e.process),user:e.user,listener:e.listener&&{proto:e.listener.proto,port:e.listener.port,container:e.listener.container?.name}};case"container":return{kind:e.kind,container:J(e.container),network:e.network};case"remote":return{kind:e.kind,route:K(e.route)}}}var K=(e)=>({...e,container:J(e.container)}),I=(e)=>({...e,process:pe(e.process),peer:rn(e.peer)}),V=(e)=>({proto:e.proto,port:e.port,addresses:e.addresses,reach:e.reach,process:pe(e.process),user:e.user,container:J(e.container),published:e.published,clients:e.clients.map(I)}),le=(e)=>({...e.iface,routes:e.routes,listeners:e.listeners.map((t)=>({proto:t.proto,port:t.port})),outbound:e.outbound.map(I),local:e.local.map(I),containers:e.containers.map((t)=>({...J(t.container),ip:t.ip}))}),ct=(e)=>({...e,everywhere:e.everywhere.map(V),interfaces:e.interfaces.map(le),dns:e.dns.map((t)=>({server:t.server,route:K(t.route)}))});var sn={json:["--json","machine-readable output"],tcp:["--tcp","TCP only"],udp:["--udp","UDP only"],all:["-a, --all","also connections that are opening or closing (TIME_WAIT…)"],"no-docker":["--no-docker","don't ask docker which containers publish ports"],"show-secrets":["--show-secrets",`don't mask the values of --token=, --password and the like
52
+ in command lines`],help:["-h, --help","show help: netbeagle COMMAND --help for one command"],version:["-v, --version","show the version"]},Y={list:{usage:["[list]"],summary:"every listening port: its process or container, and who can reach it",about:`Lists every port something listens on, with the process (or the docker container)
17
53
  behind it, the addresses it is bound to, and who can reach it: this machine only,
18
54
  every interface, or the interfaces holding its address. Ports docker publishes
19
55
  without a socket of their own are listed too. This is what netbeagle does with no
@@ -31,20 +67,20 @@ kernel's main routing table decides: this machine, a container, a network one of
31
67
  the interfaces is on, or a gateway; with the route and the source address.`,options:["json","no-docker"],examples:["netbeagle route github.com","netbeagle route 172.18.0.2"]},map:{usage:["map"],summary:"how everything connects, interface by interface",about:`The whole picture: the ports open on every interface; then each interface with
32
68
  its routes, the ports bound to it, the containers behind it, and the connections
33
69
  leaving through it; on the loopback, which local process talks to which. Ends
34
- with the DNS servers and the way to them.`,options:["json","no-docker"],examples:["netbeagle map"]}},de={ls:"list",if:"interfaces",ifaces:"interfaces",connections:"conns",graph:"map"},Xe=(e)=>{let t=[...e,"help"].map((o)=>Pt[o]).filter((o)=>!!o),n=Math.max(...t.map(([o])=>o.length));return t.map(([o,s])=>` ${o.padEnd(n)} ${s.replaceAll(`
70
+ with the DNS servers and the way to them.`,options:["json","no-docker"],examples:["netbeagle map"]}},fe={ls:"list",if:"interfaces",ifaces:"interfaces",connections:"conns",graph:"map"},at=(e)=>{let t=[...e,"help"].map((n)=>sn[n]).filter((n)=>!!n),o=Math.max(...t.map(([n])=>n.length));return t.map(([n,s])=>` ${n.padEnd(o)} ${s.replaceAll(`
35
71
  `,`
36
- ${" ".repeat(n)} `)}`)};function ue(){let e=Object.values(U),t=Math.max(...e.map((n)=>(n.usage[0]??"").length));return["Usage: netbeagle [COMMAND] [OPTIONS]","","Commands:",...e.map((n)=>` ${(n.usage[0]??"").padEnd(t)} ${n.summary}`),"","Options:",...Xe(["json","no-docker","show-secrets","version"]),"","netbeagle COMMAND --help shows one command's usage, options and examples.","Without root, sockets of other users' processes show only their user: sudo shows them all.","","From the same pack: envhound, which startup file sets each environment variable."," npx envhound",""].join(`
37
- `)}function Qe(e){let t=U[de[e]??e];if(!t)return;return[`Usage: ${t.usage.map((n)=>`netbeagle ${n}`).join(`
38
- `)}`,"",t.about,"","Options:",...Xe(t.options),"","Examples:",...t.examples.map((n)=>` ${n}`),""].join(`
39
- `)}var pe=["...........tttttttttt...........",".........tttttttttttttt.........","........tttttttccttttttt........","......tttttttttccttttttttt......",".....ttttttttttcctttttttttt.....","...eetttttttttcccctttttttttee...","..eeetttttttttccccttttttttteee..","..eeetttbbbbttccccttbbbbttteee..",".eeeetttttttttccccttttttttteeee.",".eeeettttwddttccccttwddtttteeee.",".eeeettttdddttccccttdddtttteeee.",".eeeettttdddtcccccctdddtttteeee.",".eeeettttttttcccccctttttttteeee.",".eeeettttcccccddddccccctttteeee.",".eeeetttcccccddddddcccccttteeee.",".eeeetttcccccddddddcccccttteeee.",".eeeetttccccccddddccccccttteeee.",".eeeettttccdcccddcccdcctttteeee.","..eeeeettcccdddppdddccctteeeee..","..eeeeeettccccppppcccctteeeeee..","...eeeeeetttccppppccttteeeeee...","....eeTTTTTTttttttttTTTTTTee....",".ffffTTTTTTTffffffffTTTTTTTffff.",".fkkkTbTbTbTkkkkkkkkTbTbTbTkkkf.",".fkkkookkkookkkGGkkkookkkggkkkf.",".fkkkkkkkkkkkkkkkkkkkkkkkkkkkkf.",".fkkooookooooklyylkooooklyylkkf.",".fkkooookooookllllkooookllllkkf.",".fkkkookkkookkkllkkkookkkllkkkf.",".fffffffffffffffllfffffffffllff."],le={t:["e08a3a",173],T:["e08a3a",173],e:["8c4214",94],b:["a8521a",130],c:["fbf1e3",255],d:["2b1d14",234],w:["ffffff",231],p:["f07c8a",210],f:["4a5266",240],k:["0f1218",233],o:["3a4152",238],l:["4fa3e0",74],y:["f3c969",222],g:["7fd18b",114],G:["7fd18b",114]};function Ze(e,t){let n=([c,a],d)=>t.truecolor?`${d};2;${parseInt(c.slice(0,2),16)};${parseInt(c.slice(2,4),16)};${parseInt(c.slice(4),16)}`:`${d};5;${a}`,o=(c)=>c!=="."&&(t.color||!"dwk".includes(c)),s=(c,a)=>{if(!o(c)&&!o(a))return" ";if(!t.color)return o(c)?o(a)?"█":"▀":"▄";if(!o(c))return`\x1B[${n(le[a],38)}m▄\x1B[0m`;let d="G".includes(c)?"5;":"",u=o(a)?`;${n(le[a],48)}`:"";return`\x1B[${d}${n(le[c],38)}${u}m▀\x1B[0m`},r=[];for(let c=0;c*2<pe.length;c++){let a=pe[c*2],d=pe[c*2+1]??".".repeat(a.length),u="";for(let f=0;f<a.length;f++)u+=s(a[f],d[f]);let p=u.replace(/ +$/,"");r.push({out:p,width:a.length-(u.length-p.length)})}let i=Math.floor((r.length-e.length)/2);if((t.columns||80)<38+Math.max(...e.map((c)=>c.length)))return[...r.map((c)=>` ${c.out}`),"",...e.map((c)=>c?` ${c}`:"")].join(`
72
+ ${" ".repeat(o)} `)}`)};function me(){let e=Object.values(Y),t=Math.max(...e.map((o)=>(o.usage[0]??"").length));return["Usage: netbeagle [COMMAND] [OPTIONS]","","Commands:",...e.map((o)=>` ${(o.usage[0]??"").padEnd(t)} ${o.summary}`),"","Options:",...at(["json","no-docker","show-secrets","version"]),"","netbeagle COMMAND --help shows one command's usage, options and examples.",process.platform==="win32"?"Without an administrator terminal, other users' command lines are hidden.":"Without root, sockets of other users' processes show no process: sudo shows them all.","","From the same pack: envhound, which startup file sets each environment variable."," npx envhound",""].join(`
73
+ `)}function dt(e){let t=Y[fe[e]??e];if(!t)return;return[`Usage: ${t.usage.map((o)=>`netbeagle ${o}`).join(`
74
+ `)}`,"",t.about,"","Options:",...at(t.options),"","Examples:",...t.examples.map((o)=>` ${o}`),""].join(`
75
+ `)}var he=["...........tttttttttt...........",".........tttttttttttttt.........","........tttttttccttttttt........","......tttttttttccttttttttt......",".....ttttttttttcctttttttttt.....","...eetttttttttcccctttttttttee...","..eeetttttttttccccttttttttteee..","..eeetttbbbbttccccttbbbbttteee..",".eeeetttttttttccccttttttttteeee.",".eeeettttwddttccccttwddtttteeee.",".eeeettttdddttccccttdddtttteeee.",".eeeettttdddtcccccctdddtttteeee.",".eeeettttttttcccccctttttttteeee.",".eeeettttcccccddddccccctttteeee.",".eeeetttcccccddddddcccccttteeee.",".eeeetttcccccddddddcccccttteeee.",".eeeetttccccccddddccccccttteeee.",".eeeettttccdcccddcccdcctttteeee.","..eeeeettcccdddppdddccctteeeee..","..eeeeeettccccppppcccctteeeeee..","...eeeeeetttccppppccttteeeeee...","....eeTTTTTTttttttttTTTTTTee....",".ffffTTTTTTTffffffffTTTTTTTffff.",".fkkkTbTbTbTkkkkkkkkTbTbTbTkkkf.",".fkkkookkkookkkGGkkkookkkggkkkf.",".fkkkkkkkkkkkkkkkkkkkkkkkkkkkkf.",".fkkooookooooklyylkooooklyylkkf.",".fkkooookooookllllkooookllllkkf.",".fkkkookkkookkkllkkkookkkllkkkf.",".fffffffffffffffllfffffffffllff."],ge={t:["e08a3a",173],T:["e08a3a",173],e:["8c4214",94],b:["a8521a",130],c:["fbf1e3",255],d:["2b1d14",234],w:["ffffff",231],p:["f07c8a",210],f:["4a5266",240],k:["0f1218",233],o:["3a4152",238],l:["4fa3e0",74],y:["f3c969",222],g:["7fd18b",114],G:["7fd18b",114]};function ut(e,t){let o=([i,a],u)=>t.truecolor?`${u};2;${parseInt(i.slice(0,2),16)};${parseInt(i.slice(2,4),16)};${parseInt(i.slice(4),16)}`:`${u};5;${a}`,n=(i)=>i!=="."&&(t.color||!"dwk".includes(i)),s=(i,a)=>{if(!n(i)&&!n(a))return" ";if(!t.color)return n(i)?n(a)?"█":"▀":"▄";if(!n(i))return`\x1B[${o(ge[a],38)}m▄\x1B[0m`;let u="G".includes(i)?"5;":"",p=n(a)?`;${o(ge[a],48)}`:"";return`\x1B[${u}${o(ge[i],38)}${p}m▀\x1B[0m`},r=[];for(let i=0;i*2<he.length;i++){let a=he[i*2],u=he[i*2+1]??".".repeat(a.length),p="";for(let l=0;l<a.length;l++)p+=s(a[l],u[l]);let d=p.replace(/ +$/,"");r.push({out:d,width:a.length-(p.length-d.length)})}let c=Math.floor((r.length-e.length)/2);if((t.columns||80)<38+Math.max(...e.map((i)=>i.length)))return[...r.map((i)=>` ${i.out}`),"",...e.map((i)=>i?` ${i}`:"")].join(`
40
76
  `)+`
41
- `;return r.map((c,a)=>{let d=e[a-i];return d?` ${c.out}${" ".repeat(32-c.width)} ${d}`:` ${c.out}`}).join(`
77
+ `;return r.map((i,a)=>{let u=e[a-c];return u?` ${i.out}${" ".repeat(32-i.width)} ${u}`:` ${i.out}`}).join(`
42
78
  `)+`
43
- `}var Tt=new RegExp("((?:^|\\s)--?[\\w-]*(?:token|secret|passw(?:or)?d|api[-_]?key|private[-_]?key|credential|auth(?![a-z]))[\\w-]*(?:=|\\s+)|[\\w-]*(?:token|secret|passw(?:or)?d|api[-_]?key|private[-_]?key|credential|auth(?![a-z]))[\\w-]*=)(?!-)[^\\s&]+","gi"),et=(e)=>e.replace(Tt,"$1********");var Ot="see what listens on each port, who reaches it, and how everything connects",tt="netbeagle --help";function y(e){return console.error(`netbeagle: ${e}
44
- see: ${tt}`),2}async function jt(e){let{values:t,positionals:n}=Ct({args:e,allowPositionals:!0,options:{json:{type:"boolean"},tcp:{type:"boolean"},udp:{type:"boolean"},all:{type:"boolean",short:"a"},"no-docker":{type:"boolean"},"show-secrets":{type:"boolean"},help:{type:"boolean",short:"h"},version:{type:"boolean",short:"v"}}}),o=process.stdout.isTTY,s=()=>Ze([`netbeagle ${E.version}`,"","see what listens on each","port, who reaches it, and","how everything connects"],{color:!process.env.NO_COLOR,truecolor:/^(truecolor|24bit)$/i.test(process.env.COLORTERM??""),columns:process.stdout.columns}),r=n[0]==="help",i=(r?n[1]:t.help?n[0]:void 0)??void 0;if(t.help||r){if(i){let l=Qe(i);if(!l)return y(`no command '${i}'`);return process.stdout.write(l),0}return process.stdout.write(o?`${s()}
45
- ${ue()}`:`netbeagle ${E.version}
46
- ${Ot}
79
+ `}var cn=new RegExp("((?:^|\\s)--?[\\w-]*(?:token|secret|passw(?:or)?d|api[-_]?key|private[-_]?key|credential|auth(?![a-z]))[\\w-]*(?:=|\\s+)|[\\w-]*(?:token|secret|passw(?:or)?d|api[-_]?key|private[-_]?key|credential|auth(?![a-z]))[\\w-]*=)(?!-)[^\\s&]+","gi"),pt=(e)=>e.replace(cn,"$1********");var pn="see what listens on each port, who reaches it, and how everything connects",lt="netbeagle --help";function v(e){return console.error(`netbeagle: ${e}
80
+ see: ${lt}`),2}async function ln(e){let{values:t,positionals:o}=un({args:e,allowPositionals:!0,options:{json:{type:"boolean"},tcp:{type:"boolean"},udp:{type:"boolean"},all:{type:"boolean",short:"a"},"no-docker":{type:"boolean"},"show-secrets":{type:"boolean"},help:{type:"boolean",short:"h"},version:{type:"boolean",short:"v"}}}),n=process.stdout.isTTY,s=()=>ut([`netbeagle ${O.version}`,"","see what listens on each","port, who reaches it, and","how everything connects"],{color:!process.env.NO_COLOR,truecolor:/^(truecolor|24bit)$/i.test(process.env.COLORTERM??""),columns:process.stdout.columns}),r=o[0]==="help",c=(r?o[1]:t.help?o[0]:void 0)??void 0;if(t.help||r){if(c){let f=dt(c);if(!f)return v(`no command '${c}'`);return process.stdout.write(f),0}return process.stdout.write(n?`${s()}
81
+ ${me()}`:`netbeagle ${O.version}
82
+ ${pn}
47
83
 
48
- ${ue()}`),0}if(t.version)return process.stdout.write(o?s():`${E.version}
49
- `),0;let[c="list",...a]=n,d=de[c]??c;if(!U[d])return y(`no command '${c}'`);if(tt=`netbeagle ${d} --help`,process.platform!=="linux")return console.error("netbeagle reads Linux's /proc and /sys, so it runs on Linux and WSL for now; macOS and Windows are planned"),2;let u=process.stdout.isTTY,p=We(t["no-docker"]?{containers:[]}:{});if(!t["show-secrets"])for(let l of p.processes)l.cmd=et(l.cmd);let f={color:u&&!process.env.NO_COLOR,width:u?process.stdout.columns:void 0,wsl:p.environment==="wsl"},m=(l,h)=>console.log(t.json?JSON.stringify(h(),null,2):l()),x=(l)=>t.tcp||t.udp?!!t[l]:!0,T=()=>{let l=Se(p);if(l&&!t.json)console.error(g({...f,color:!!process.stderr.isTTY&&!process.env.NO_COLOR}).dim(`
50
- ${l} sockets belong to other users' processes: sudo shows which`))};switch(d){case"list":{let l=R(p).filter((h)=>x(h.proto));return m(()=>He(l,f),()=>l.map(G)),T(),0}case"who":{let l=Number(a[0]?.replace(/^:/,""));if(!a[0]||!Number.isInteger(l)||l<1||l>65535)return y("who takes a port number, e.g. netbeagle who 5432");let h=xe(p,l);return m(()=>Ke(h,f),()=>({port:l,listeners:h.listeners.map(G),outbound:h.outbound.map(w)})),h.listeners.length||h.outbound.length?0:1}case"conns":{let l=P(p).filter((h)=>x(h.proto)).filter((h)=>t.all||h.state==="ESTABLISHED");return m(()=>Ue(l,f),()=>l.map(w)),T(),0}case"interfaces":{let l=ee(p);return m(()=>Ge(l.interfaces,l.idle,f),()=>l.interfaces.map(ae)),0}case"route":{let l=a[0];if(!l)return y("route takes a host name or an address, e.g. netbeagle route github.com");let h=l;if(!Et(l))try{h=(await Nt(l)).address}catch{return console.error(`netbeagle: cannot resolve ${l}`),1}let N=j(p,h);return m(()=>Ye(N,l,f),()=>({host:l,...V(N)})),N.kind==="unreachable"?1:0}case"map":{let l=ee(p);return m(()=>ze(l,f),()=>qe(l)),0}}return y(`no command '${c}'`)}jt(process.argv.slice(2)).then((e)=>{process.exitCode=e},(e)=>{if(e instanceof Error&&"code"in e&&String(e.code).startsWith("ERR_PARSE_ARGS"))process.exitCode=y(e.message);else console.error(`netbeagle: ${e instanceof Error?e.message:String(e)}`),process.exitCode=1});
84
+ ${me()}`),0}if(t.version)return process.stdout.write(n?s():`${O.version}
85
+ `),0;let[i="list",...a]=o,u=fe[i]??i;if(!Y[u])return v(`no command '${i}'`);if(lt=`netbeagle ${u} --help`,!["linux","darwin","win32"].includes(process.platform))return console.error(`netbeagle runs on Linux, macOS and Windows, not ${process.platform}`),2;let p=process.stdout.isTTY,d=Ye(t["no-docker"]?{containers:[]}:{});if(!t["show-secrets"])for(let f of d.processes)f.cmd=pt(f.cmd);let l={color:p&&!process.env.NO_COLOR,width:p?process.stdout.columns:void 0,wsl:d.environment==="wsl",windows:d.environment==="windows"},m=(f,h)=>console.log(t.json?JSON.stringify(h(),null,2):f()),g=(f)=>t.tcp||t.udp?!!t[f]:!0,w=()=>{let f=Ne(d);if(f&&!t.json)console.error(y({...l,color:!!process.stderr.isTTY&&!process.env.NO_COLOR}).dim(`
86
+ ${f} sockets belong to other users' processes: ${H(l)} to see which`))};switch(u){case"list":{let f=R(d).filter((h)=>g(h.proto));return m(()=>Ze(f,l),()=>f.map(V)),w(),0}case"who":{let f=Number(a[0]?.replace(/^:/,""));if(!a[0]||!Number.isInteger(f)||f<1||f>65535)return v("who takes a port number, e.g. netbeagle who 5432");let h=Pe(d,f);return m(()=>rt(h,l),()=>({port:f,listeners:h.listeners.map(V),outbound:h.outbound.map(I)})),h.listeners.length||h.outbound.length?0:1}case"conns":{let f=E(d).filter((h)=>g(h.proto)).filter((h)=>t.all||h.state==="ESTABLISHED");return m(()=>ot(f,l),()=>f.map(I)),w(),0}case"interfaces":{let f=re(d);return m(()=>nt(f.interfaces,f.idle,l),()=>f.interfaces.map(le)),0}case"route":{let f=a[0];if(!f)return v("route takes a host name or an address, e.g. netbeagle route github.com");let h=f;if(!dn(f))try{h=(await an(f)).address}catch{return console.error(`netbeagle: cannot resolve ${f}`),1}let k=A(d,h);return m(()=>st(k,f,l),()=>({host:f,...K(k)})),k.kind==="unreachable"?1:0}case"map":{let f=re(d);return m(()=>it(f,l),()=>ct(f)),0}}return v(`no command '${i}'`)}ln(process.argv.slice(2)).then((e)=>{process.exitCode=e},(e)=>{if(e instanceof Error&&"code"in e&&String(e.code).startsWith("ERR_PARSE_ARGS"))process.exitCode=v(e.message);else console.error(`netbeagle: ${e instanceof Error?e.message:String(e)}`),process.exitCode=1});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "netbeagle",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "See which process listens on each port, which interfaces reach it, and how everything on your machine connects",
5
5
  "keywords": [
6
6
  "network",
@@ -10,7 +10,10 @@
10
10
  "lsof",
11
11
  "interfaces",
12
12
  "docker",
13
- "routes"
13
+ "routes",
14
+ "linux",
15
+ "macos",
16
+ "windows"
14
17
  ],
15
18
  "license": "MIT",
16
19
  "author": "Michel Salib",