nc://ports

v0.1.0Pinned
ThomSharktools/nodecode-ports: Who holds a local port: the TCP/UDP endpoints, their PIDs and process paths, as a tool and /ports. - a rewrite of Yicheng-Mark/hermes-windows-plugins (MIT), credited to Yicheng-Mark57d8f8e· 9 Nov1 commit
test2 filescell.lisp12.9 KBtable.lisp24.4 KB

nodecode-ports

Who holds a local port. A Nodecode rewrite of Yicheng-Mark/hermes-windows-plugins by Yicheng-Mark.

A bind failure names a port and nothing else. One call here answers the rest: the local TCP and UDP endpoints, the owning PID, its image name and its path, and who else is on the same binary. The rows are the ones netstat -ano prints, with the process already joined to them, so nothing depends on console output or on the machine's display language.

The plugin read Windows' tables only. This cell keeps that path and adds the two others from the same question: /proc on Linux and lsof on macOS. All three are read-only — nothing is opened, bound or connected, and on Windows and Linux no child process runs at all.

Get it

Tell the agent "add the ports cell", or run nodecode add nc://ports.

Set it up

Nothing is required. Two settings in the cell's ports section shape the answer, both under /settings or the Settings tab:

Setting Default What it does
limit 100 the most endpoints one answer carries; a call may lower or raise it, to 500
show_paths on whether an answer carries each process's image path

macOS needs lsof, which ships with the system. Linux reads /proc, and a process whose descriptors this user cannot read keeps its endpoint and reports no owner rather than failing.

Use it

Ask in words — "what is holding port 3000", "is anything listening on 8086", "which process owns this connection" — and the model calls win_port_owners (the name the original plugin used, so a prompt written for it still works). Or call it yourself from eval:

(ports:owners 3000)                      ; the endpoint holding port 3000
(ports:owners :process "node")            ; whatever node holds
(ports:owners :state "listen")            ; every listener
(ports:owners :ports '(3000 8086))        ; two ports at once
(ports:owners :protocol "udp" :limit 20)  ; the UDP table, twenty rows

The answer is JSON, field for field the original's:

{"ok": true, "matched": 1, "listeners": 1, "total_endpoints": 121,
 "entries": [{"protocol": "tcp", "address": "127.0.0.1", "port": 3000,
              "state": "listen", "pid": 496, "process": "node",
              "path": "/usr/local/bin/node",
              "remote_address": "0.0.0.0", "remote_port": 0}],
 "by_process": {"node": 1},
 "notes": ["1 endpoint matched out of 121 in the table"]}

Filters, the same six the plugin took: port and ports (local endpoints), process (part of the image name or path, case-insensitive), protocol (tcp, udp, both), state (listen, established, any TCP state by name, all; a UDP row reports bound), include_ipv6 (on by default) and limit.

/ports prints the same rows as lines: /ports 3000, /ports node, /ports listen, /ports for the whole table. (help :ports) has the rest.

What changed in the rewrite

The plugin Here
Python, ctypes, iphlpapi.dll Common Lisp, sb-alien, the same two tables and the same QueryFullProcessImageName right (PROCESS_QUERY_LIMITED_INFORMATION)
Windows only Windows, Linux (/proc and its socket inodes) and macOS (lsof -F)
a tool only the tool, /ports, and (help :ports)
everything in one answer limit and show_paths in the cell's section

The answer's shape, the six filters, the by_process summary and the notes are unchanged. macOS has no in-process table without a kext, so that one platform runs lsof.

License

MIT, Copyright (c) 2026 Yicheng-Mark: see LICENSE and NOTICE.

Add nc://ports

  1. 1

    Install Nodecode

    Once per machine.

    $ curl -fsSL https://nodecode.ai/install.sh | sh
  2. 2

    Add the cell

    Pinned at 57d8f8e.

    $ nodecode add nc://ports

    Or tell the agent add the ports cell.