@qzda/port 0.1.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 ADDED
@@ -0,0 +1,47 @@
1
+ # @qzda/port
2
+
3
+ Cross-platform CLI tool to list listening TCP ports and the programs occupying them.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -g @qzda/port
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```bash
14
+ port list
15
+ ```
16
+
17
+ Example output:
18
+
19
+ ```
20
+ PID PORT PROGRAM
21
+ ----- ---- -------
22
+ 1234 22 sshd
23
+ 5678 80 nginx
24
+ 12345 3000 node
25
+ ```
26
+
27
+ ## Supported Platforms
28
+
29
+ | Platform | Method |
30
+ |----------|--------|
31
+ | Linux | `ss -tlnp` (fallback `netstat -tlnp`) |
32
+ | macOS | `lsof -iTCP -sTCP:LISTEN -P -n` |
33
+ | Windows | `netstat -ano` + `tasklist` |
34
+
35
+ ## Development
36
+
37
+ ```bash
38
+ git clone git@github.com:qzda/port.git
39
+ cd port
40
+ npm install
41
+ npm run build
42
+ node dist/index.js list
43
+ ```
44
+
45
+ ## License
46
+
47
+ MIT
package/dist/format.js ADDED
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.formatTable = formatTable;
4
+ function formatTable(entries) {
5
+ if (entries.length === 0) {
6
+ return 'No listening ports found.';
7
+ }
8
+ const pidHeader = 'PID';
9
+ const portHeader = 'PORT';
10
+ const programHeader = 'PROGRAM';
11
+ const pidWidth = Math.max(pidHeader.length, ...entries.map((e) => String(e.pid).length));
12
+ const portWidth = Math.max(portHeader.length, ...entries.map((e) => String(e.port).length));
13
+ const programWidth = Math.max(programHeader.length, ...entries.map((e) => e.program.length));
14
+ const header = `${pidHeader.padEnd(pidWidth)} ${portHeader.padEnd(portWidth)} ${programHeader}`;
15
+ const separator = `${'-'.repeat(pidWidth)} ${'-'.repeat(portWidth)} ${'-'.repeat(programWidth)}`;
16
+ const rows = entries.map((e) => `${String(e.pid).padEnd(pidWidth)} ${String(e.port).padEnd(portWidth)} ${e.program}`);
17
+ return [header, separator, ...rows].join('\n');
18
+ }
19
+ //# sourceMappingURL=format.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.js","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":";;AAEA,kCA+BC;AA/BD,SAAgB,WAAW,CAAC,OAAoB;IAC9C,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,2BAA2B,CAAC;IACrC,CAAC;IAED,MAAM,SAAS,GAAG,KAAK,CAAC;IACxB,MAAM,UAAU,GAAG,MAAM,CAAC;IAC1B,MAAM,aAAa,GAAG,SAAS,CAAC;IAEhC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CACvB,SAAS,CAAC,MAAM,EAChB,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAC5C,CAAC;IACF,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CACxB,UAAU,CAAC,MAAM,EACjB,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAC7C,CAAC;IACF,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAC3B,aAAa,CAAC,MAAM,EACpB,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CACxC,CAAC;IAEF,MAAM,MAAM,GAAG,GAAG,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,UAAU,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,aAAa,EAAE,CAAC;IAClG,MAAM,SAAS,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,GAAG,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE,CAAC;IAEnG,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CACtB,CAAC,CAAC,EAAE,EAAE,CACJ,GAAG,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,CACzF,CAAC;IAEF,OAAO,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACjD,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const commander_1 = require("commander");
5
+ const scanner_js_1 = require("./scanner.js");
6
+ const format_js_1 = require("./format.js");
7
+ const program = new commander_1.Command();
8
+ program
9
+ .name('port')
10
+ .description('List listening TCP ports on the current machine')
11
+ .version('0.1.0');
12
+ program
13
+ .command('list')
14
+ .description('List all listening TCP ports with PID, port number, and program name')
15
+ .action(async () => {
16
+ try {
17
+ const entries = await (0, scanner_js_1.scanPorts)();
18
+ console.log((0, format_js_1.formatTable)(entries));
19
+ }
20
+ catch (err) {
21
+ console.error('Error:', err instanceof Error ? err.message : err);
22
+ process.exit(1);
23
+ }
24
+ });
25
+ // Show help if no command is given
26
+ program.parse();
27
+ if (!process.argv.slice(2).length) {
28
+ program.outputHelp();
29
+ }
30
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAEA,yCAAoC;AACpC,6CAAyC;AACzC,2CAA0C;AAE1C,MAAM,OAAO,GAAG,IAAI,mBAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,MAAM,CAAC;KACZ,WAAW,CAAC,iDAAiD,CAAC;KAC9D,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,OAAO;KACJ,OAAO,CAAC,MAAM,CAAC;KACf,WAAW,CAAC,sEAAsE,CAAC;KACnF,MAAM,CAAC,KAAK,IAAI,EAAE;IACjB,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,IAAA,sBAAS,GAAE,CAAC;QAClC,OAAO,CAAC,GAAG,CAAC,IAAA,uBAAW,EAAC,OAAO,CAAC,CAAC,CAAC;IACpC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QAClE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC,CAAC,CAAC;AAEL,mCAAmC;AACnC,OAAO,CAAC,KAAK,EAAE,CAAC;AAEhB,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IAClC,OAAO,CAAC,UAAU,EAAE,CAAC;AACvB,CAAC"}
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.scan = scan;
4
+ const child_process_1 = require("child_process");
5
+ function execCommand(command, args) {
6
+ return new Promise((resolve, reject) => {
7
+ (0, child_process_1.execFile)(command, args, { timeout: 15000 }, (err, stdout) => {
8
+ if (err) {
9
+ reject(err);
10
+ }
11
+ else {
12
+ resolve(stdout);
13
+ }
14
+ });
15
+ });
16
+ }
17
+ /**
18
+ * Parse `lsof -iTCP -sTCP:LISTEN -P -n` output.
19
+ * Example lines:
20
+ * COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
21
+ * node 12345 user 23u IPv4 0x1234abcd 0t0 TCP *:3000 (LISTEN)
22
+ * sshd 678 root 5u IPv6 0xabcd1234 0t0 TCP *:22 (LISTEN)
23
+ * nginx 9012 root 6u IPv4 0xdefg5678 0t0 TCP 127.0.0.1:8080 (LISTEN)
24
+ * java 5555 user 123u IPv6 0xaaaa1111 0t0 TCP [::1]:9090 (LISTEN)
25
+ */
26
+ function parseLsof(output) {
27
+ const entries = [];
28
+ const seen = new Set();
29
+ // Regex captures: COMMAND, PID, and the NAME field (address:port before (LISTEN))
30
+ const re = /^(\S+)\s+(\d+)\s+.*TCP\s+(\S+)\s+\(LISTEN\)/gm;
31
+ let match;
32
+ while ((match = re.exec(output)) !== null) {
33
+ const program = match[1];
34
+ const pid = parseInt(match[2], 10);
35
+ const nameField = match[3];
36
+ // Extract port from NAME field: *:3000, 127.0.0.1:8080, [::1]:9090
37
+ const portMatch = nameField.match(/:(\d+)$/);
38
+ if (!portMatch)
39
+ continue;
40
+ const port = parseInt(portMatch[1], 10);
41
+ const key = `${pid}:${port}`;
42
+ if (!seen.has(key)) {
43
+ seen.add(key);
44
+ entries.push({ pid, port, program });
45
+ }
46
+ }
47
+ return entries;
48
+ }
49
+ async function scan() {
50
+ try {
51
+ const stdout = await execCommand('lsof', ['-iTCP', '-sTCP:LISTEN', '-P', '-n']);
52
+ return parseLsof(stdout);
53
+ }
54
+ catch (err) {
55
+ if (err?.code === 'ENOENT') {
56
+ throw new Error('`lsof` is not installed. Install it to use this tool:\n' +
57
+ ' macOS: it should be available by default. If missing, install Xcode CLI tools.\n' +
58
+ ' Linux: apt install lsof / yum install lsof');
59
+ }
60
+ throw err;
61
+ }
62
+ }
63
+ //# sourceMappingURL=darwin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"darwin.js","sourceRoot":"","sources":["../../src/parsers/darwin.ts"],"names":[],"mappings":";;AAoDA,oBAcC;AAlED,iDAAyC;AAGzC,SAAS,WAAW,CAAC,OAAe,EAAE,IAAc;IAClD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,IAAA,wBAAQ,EAAC,OAAO,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,CAAC,GAAiB,EAAE,MAAc,EAAE,EAAE;YAChF,IAAI,GAAG,EAAE,CAAC;gBACR,MAAM,CAAC,GAAG,CAAC,CAAC;YACd,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,MAAM,CAAC,CAAC;YAClB,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,SAAS,CAAC,MAAc;IAC/B,MAAM,OAAO,GAAgB,EAAE,CAAC;IAChC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAE/B,kFAAkF;IAClF,MAAM,EAAE,GAAG,+CAA+C,CAAC;IAE3D,IAAI,KAA6B,CAAC;IAClC,OAAO,CAAC,KAAK,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAC1C,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACzB,MAAM,GAAG,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACnC,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QAE3B,mEAAmE;QACnE,MAAM,SAAS,GAAG,SAAS,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QAC7C,IAAI,CAAC,SAAS;YAAE,SAAS;QAEzB,MAAM,IAAI,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACxC,MAAM,GAAG,GAAG,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;QAC7B,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YACnB,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACd,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;QACvC,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAEM,KAAK,UAAU,IAAI;IACxB,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;QAChF,OAAO,SAAS,CAAC,MAAM,CAAC,CAAC;IAC3B,CAAC;IAAC,OAAO,GAAQ,EAAE,CAAC;QAClB,IAAI,GAAG,EAAE,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC3B,MAAM,IAAI,KAAK,CACb,yDAAyD;gBACzD,oFAAoF;gBACpF,8CAA8C,CAC/C,CAAC;QACJ,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC"}
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.scan = scan;
4
+ const child_process_1 = require("child_process");
5
+ function execCommand(command, args) {
6
+ return new Promise((resolve, reject) => {
7
+ (0, child_process_1.execFile)(command, args, { timeout: 15000 }, (err, stdout) => {
8
+ if (err) {
9
+ reject(err);
10
+ }
11
+ else {
12
+ resolve(stdout);
13
+ }
14
+ });
15
+ });
16
+ }
17
+ /**
18
+ * Parse `ss -tlnp` output.
19
+ * Example lines:
20
+ * LISTEN 0 128 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=1234,fd=3))
21
+ * LISTEN 0 128 [::]:80 [::]:* users:(("nginx",pid=5678,fd=6))
22
+ */
23
+ function parseSs(output) {
24
+ const entries = [];
25
+ const seen = new Set();
26
+ // Matches: address:port address:* users:(("name",pid=NNN,...
27
+ const re = /\S+:(\d+)\s+\S+:\*\s+users:\(\(\"(\S+)\",pid=(\d+)/g;
28
+ for (const line of output.split('\n')) {
29
+ let match;
30
+ while ((match = re.exec(line)) !== null) {
31
+ const port = parseInt(match[1], 10);
32
+ const program = match[2];
33
+ const pid = parseInt(match[3], 10);
34
+ const key = `${pid}:${port}`;
35
+ if (!seen.has(key)) {
36
+ seen.add(key);
37
+ entries.push({ pid, port, program });
38
+ }
39
+ }
40
+ }
41
+ return entries;
42
+ }
43
+ /**
44
+ * Parse `netstat -tlnp` output (fallback).
45
+ * Example lines:
46
+ * tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 1234/sshd
47
+ * tcp6 0 0 :::80 :::* LISTEN 5678/nginx
48
+ */
49
+ function parseNetstat(output) {
50
+ const entries = [];
51
+ const seen = new Set();
52
+ const re = /^\S+\s+\d+\s+\d+\s+\S+:(\d+)\s+\S+\s+LISTEN\s+(\d+)\/(\S+)/gm;
53
+ let match;
54
+ while ((match = re.exec(output)) !== null) {
55
+ const port = parseInt(match[1], 10);
56
+ const pid = parseInt(match[2], 10);
57
+ const program = match[3];
58
+ const key = `${pid}:${port}`;
59
+ if (!seen.has(key)) {
60
+ seen.add(key);
61
+ entries.push({ pid, port, program });
62
+ }
63
+ }
64
+ return entries;
65
+ }
66
+ async function scan() {
67
+ // Try `ss -tlnp` first
68
+ try {
69
+ const stdout = await execCommand('ss', ['-tlnp']);
70
+ return parseSs(stdout);
71
+ }
72
+ catch {
73
+ // ss failed, try netstat as fallback
74
+ }
75
+ try {
76
+ const stdout = await execCommand('netstat', ['-tlnp']);
77
+ return parseNetstat(stdout);
78
+ }
79
+ catch {
80
+ // netstat also failed
81
+ }
82
+ throw new Error('Neither `ss` nor `netstat` is available. ' +
83
+ 'Install iproute2 (`ss`) or net-tools (`netstat`) to use this tool.');
84
+ }
85
+ //# sourceMappingURL=linux.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linux.js","sourceRoot":"","sources":["../../src/parsers/linux.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.scan = scan;
4
+ const child_process_1 = require("child_process");
5
+ function execCommand(command, args) {
6
+ return new Promise((resolve, reject) => {
7
+ (0, child_process_1.execFile)(command, args, { timeout: 15000 }, (err, stdout) => {
8
+ if (err) {
9
+ reject(err);
10
+ }
11
+ else {
12
+ resolve(stdout);
13
+ }
14
+ });
15
+ });
16
+ }
17
+ /**
18
+ * Parse `netstat -ano` output for LISTENING TCP ports.
19
+ * Example lines:
20
+ * TCP 0.0.0.0:3000 0.0.0.0:0 LISTENING 12345
21
+ * TCP 127.0.0.1:8080 0.0.0.0:0 LISTENING 5678
22
+ * TCP [::]:80 [::]:0 LISTENING 4
23
+ */
24
+ function parseNetstat(output) {
25
+ // pid -> ports[]
26
+ const pidPorts = new Map();
27
+ const re = /^\s*(?:TCP|TCPv6)\s+\[?([^\]]+)\]?:(\d+)\s+.*LISTENING\s+(\d+)/gm;
28
+ let match;
29
+ while ((match = re.exec(output)) !== null) {
30
+ const port = parseInt(match[2], 10);
31
+ const pid = parseInt(match[3], 10);
32
+ const ports = pidPorts.get(pid);
33
+ if (ports) {
34
+ if (!ports.includes(port)) {
35
+ ports.push(port);
36
+ }
37
+ }
38
+ else {
39
+ pidPorts.set(pid, [port]);
40
+ }
41
+ }
42
+ return pidPorts;
43
+ }
44
+ /**
45
+ * Resolve a single PID to a process name using `tasklist`.
46
+ * Example output: `"node.exe","12345","Console","1","123,456 K"`
47
+ */
48
+ async function resolveProcessName(pid) {
49
+ // Special PIDs on Windows
50
+ if (pid === 0)
51
+ return 'System Idle Process';
52
+ if (pid === 4)
53
+ return 'System';
54
+ try {
55
+ const stdout = await execCommand('tasklist', [
56
+ '/FI', `PID eq ${pid}`,
57
+ '/FO', 'CSV',
58
+ '/NH',
59
+ ]);
60
+ // Parse CSV: "program.exe","pid","session","session#","mem usage"
61
+ const match = stdout.match(/^"([^"]+)"/m);
62
+ if (match) {
63
+ return match[1];
64
+ }
65
+ }
66
+ catch {
67
+ // tasklist failed for this PID
68
+ }
69
+ return '<unknown>';
70
+ }
71
+ async function scan() {
72
+ // Phase 1: netstat -ano to get listening ports and PIDs
73
+ const netstatOutput = await execCommand('netstat', ['-ano']);
74
+ const pidPorts = parseNetstat(netstatOutput);
75
+ if (pidPorts.size === 0)
76
+ return [];
77
+ // Phase 2: resolve each PID to a process name
78
+ const entries = [];
79
+ for (const [pid, ports] of pidPorts) {
80
+ const program = await resolveProcessName(pid);
81
+ for (const port of ports) {
82
+ entries.push({ pid, port, program });
83
+ }
84
+ }
85
+ return entries;
86
+ }
87
+ //# sourceMappingURL=win32.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"win32.js","sourceRoot":"","sources":["../../src/parsers/win32.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.scanPorts = scanPorts;
4
+ async function scanPorts() {
5
+ switch (process.platform) {
6
+ case 'linux':
7
+ return (await import('./parsers/linux.js')).scan();
8
+ case 'darwin':
9
+ return (await import('./parsers/darwin.js')).scan();
10
+ case 'win32':
11
+ return (await import('./parsers/win32.js')).scan();
12
+ default:
13
+ throw new Error(`Unsupported platform: ${process.platform}`);
14
+ }
15
+ }
16
+ //# sourceMappingURL=scanner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scanner.js","sourceRoot":"","sources":["../src/scanner.ts"],"names":[],"mappings":";;AAEA,8BAWC;AAXM,KAAK,UAAU,SAAS;IAC7B,QAAQ,OAAO,CAAC,QAAQ,EAAE,CAAC;QACzB,KAAK,OAAO;YACV,OAAO,CAAC,MAAM,MAAM,CAAC,oBAAoB,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACrD,KAAK,QAAQ;YACX,OAAO,CAAC,MAAM,MAAM,CAAC,qBAAqB,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACtD,KAAK,OAAO;YACV,OAAO,CAAC,MAAM,MAAM,CAAC,oBAAoB,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACrD;YACE,MAAM,IAAI,KAAK,CAAC,yBAAyB,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;IACjE,CAAC;AACH,CAAC"}
package/dist/types.js ADDED
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@qzda/port",
3
+ "version": "0.1.0",
4
+ "description": "Cross-platform CLI tool to list listening TCP ports",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/qzda/port.git"
9
+ },
10
+ "bin": {
11
+ "port": "./dist/index.js"
12
+ },
13
+ "files": [
14
+ "dist/"
15
+ ],
16
+ "scripts": {
17
+ "build": "tsc",
18
+ "prepublishOnly": "npm run build",
19
+ "start": "node dist/index.js"
20
+ },
21
+ "dependencies": {
22
+ "commander": "^14.0.0"
23
+ },
24
+ "devDependencies": {
25
+ "@types/node": "^22.0.0",
26
+ "typescript": "^5.8.0"
27
+ },
28
+ "engines": {
29
+ "node": ">=18.0.0"
30
+ }
31
+ }