porthawk-core 0.1.6 → 0.1.7
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/dist/index.cjs +61 -45
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +61 -45
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -64,79 +64,95 @@ function isSystemProcess(processName) {
|
|
|
64
64
|
|
|
65
65
|
// src/detect.ts
|
|
66
66
|
var execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
|
|
67
|
+
var PROCESS_TABLE_MAX_BUFFER = 10 * 1024 * 1024;
|
|
67
68
|
function isValidPid(pid) {
|
|
68
69
|
return typeof pid === "number" && Number.isInteger(pid) && pid > 0;
|
|
69
70
|
}
|
|
70
71
|
function normalizeProtocol(raw) {
|
|
71
72
|
return raw.toLowerCase().startsWith("udp") ? "udp" : "tcp";
|
|
72
73
|
}
|
|
73
|
-
async function
|
|
74
|
-
const { stdout } = await execFileAsync("ps", ["-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
74
|
+
async function queryAllUnixProcesses() {
|
|
75
|
+
const { stdout } = await execFileAsync("ps", ["-A", "-o", "pid=,ppid=,comm=,args="], {
|
|
76
|
+
maxBuffer: PROCESS_TABLE_MAX_BUFFER
|
|
77
|
+
});
|
|
78
|
+
const table = /* @__PURE__ */ new Map();
|
|
79
|
+
for (const line of stdout.split("\n")) {
|
|
80
|
+
const trimmed = line.trim();
|
|
81
|
+
if (!trimmed) continue;
|
|
82
|
+
const [pidToken, ppidToken, commToken, ...rest] = trimmed.split(/\s+/);
|
|
83
|
+
const pid = Number(pidToken);
|
|
84
|
+
if (!isValidPid(pid)) continue;
|
|
85
|
+
const comm = commToken ?? "";
|
|
86
|
+
table.set(pid, {
|
|
87
|
+
ppid: Number(ppidToken),
|
|
88
|
+
comm,
|
|
89
|
+
args: rest.length > 0 ? rest.join(" ") : comm
|
|
90
|
+
});
|
|
88
91
|
}
|
|
89
|
-
return
|
|
92
|
+
return table;
|
|
93
|
+
}
|
|
94
|
+
function resolveUnixProcessInfo(pid, table) {
|
|
95
|
+
const entry = table.get(pid);
|
|
96
|
+
if (!entry) {
|
|
97
|
+
throw new Error(`No process found for pid ${pid}`);
|
|
98
|
+
}
|
|
99
|
+
const parentEntry = isValidPid(entry.ppid) ? table.get(entry.ppid) : void 0;
|
|
100
|
+
return {
|
|
101
|
+
pid,
|
|
102
|
+
processName: entry.comm,
|
|
103
|
+
command: entry.args,
|
|
104
|
+
parentName: parentEntry?.comm ?? ""
|
|
105
|
+
};
|
|
90
106
|
}
|
|
91
|
-
async function
|
|
92
|
-
const script =
|
|
93
|
-
const { stdout } = await execFileAsync("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", script]
|
|
107
|
+
async function queryAllWindowsProcesses() {
|
|
108
|
+
const script = "Get-CimInstance Win32_Process | Select-Object ProcessId,Name,CommandLine,ParentProcessId | ConvertTo-Json -Compress";
|
|
109
|
+
const { stdout } = await execFileAsync("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", script], {
|
|
110
|
+
maxBuffer: PROCESS_TABLE_MAX_BUFFER
|
|
111
|
+
});
|
|
94
112
|
const trimmed = stdout.trim();
|
|
95
|
-
if (!trimmed) return
|
|
96
|
-
|
|
113
|
+
if (!trimmed) return /* @__PURE__ */ new Map();
|
|
114
|
+
const parsed = JSON.parse(trimmed);
|
|
115
|
+
const records = Array.isArray(parsed) ? parsed : [parsed];
|
|
116
|
+
const table = /* @__PURE__ */ new Map();
|
|
117
|
+
for (const record of records) {
|
|
118
|
+
if (isValidPid(record.ProcessId)) {
|
|
119
|
+
table.set(record.ProcessId, record);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return table;
|
|
97
123
|
}
|
|
98
|
-
|
|
99
|
-
const record =
|
|
124
|
+
function resolveWindowsProcessInfo(pid, table) {
|
|
125
|
+
const record = table.get(pid);
|
|
100
126
|
if (!record) {
|
|
101
127
|
throw new Error(`No process found for pid ${pid}`);
|
|
102
128
|
}
|
|
103
|
-
|
|
104
|
-
if (isValidPid(record.ParentProcessId)) {
|
|
105
|
-
try {
|
|
106
|
-
const parentRecord = await queryWindowsProcess(record.ParentProcessId);
|
|
107
|
-
parentName = parentRecord?.Name ?? "";
|
|
108
|
-
} catch {
|
|
109
|
-
parentName = "";
|
|
110
|
-
}
|
|
111
|
-
}
|
|
129
|
+
const parentRecord = isValidPid(record.ParentProcessId) ? table.get(record.ParentProcessId) : void 0;
|
|
112
130
|
return {
|
|
113
131
|
pid,
|
|
114
132
|
processName: record.Name ?? "",
|
|
115
133
|
command: record.CommandLine ?? record.Name ?? "",
|
|
116
|
-
parentName
|
|
134
|
+
parentName: parentRecord?.Name ?? ""
|
|
117
135
|
};
|
|
118
136
|
}
|
|
119
|
-
async function getProcessInfo(pid) {
|
|
120
|
-
return process.platform === "win32" ? getWindowsProcessInfo(pid) : getUnixProcessInfo(pid);
|
|
121
|
-
}
|
|
122
137
|
async function getListeningPorts() {
|
|
123
138
|
const connections = await si.networkConnections();
|
|
124
139
|
const listening = connections.filter(
|
|
125
140
|
(conn) => conn.state?.toUpperCase() === "LISTEN" && isValidPid(conn.pid)
|
|
126
141
|
);
|
|
127
|
-
|
|
142
|
+
if (listening.length === 0) {
|
|
143
|
+
return [];
|
|
144
|
+
}
|
|
145
|
+
const isWindows = process.platform === "win32";
|
|
146
|
+
const processTable = isWindows ? await queryAllWindowsProcesses() : await queryAllUnixProcesses();
|
|
128
147
|
const result = [];
|
|
129
148
|
for (const conn of listening) {
|
|
130
149
|
const pid = conn.pid;
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
150
|
+
let processInfo;
|
|
151
|
+
try {
|
|
152
|
+
processInfo = isWindows ? resolveWindowsProcessInfo(pid, processTable) : resolveUnixProcessInfo(pid, processTable);
|
|
153
|
+
} catch {
|
|
154
|
+
continue;
|
|
137
155
|
}
|
|
138
|
-
const processInfo = processInfoByPid.get(pid);
|
|
139
|
-
if (!processInfo) continue;
|
|
140
156
|
const port = Number(conn.localPort);
|
|
141
157
|
if (!Number.isInteger(port)) continue;
|
|
142
158
|
result.push({
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/detect.ts","../src/classify.ts","../src/kill.ts"],"sourcesContent":["export { getListeningPorts } from './detect.js';\nexport { killProcess } from './kill.js';\nexport { classifyOrigin, isSystemProcess } from './classify.js';\nexport type { PortInfo, ProcessInfo, Protocol, Origin, ClassifyRule } from './types.js';\n","import { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport * as si from 'systeminformation';\nimport { classifyOrigin } from './classify.js';\nimport type { PortInfo, Protocol, ProcessInfo } from './types.js';\n\nconst execFileAsync = promisify(execFile);\n\nfunction isValidPid(pid: unknown): pid is number {\n return typeof pid === 'number' && Number.isInteger(pid) && pid > 0;\n}\n\nfunction normalizeProtocol(raw: string): Protocol {\n return raw.toLowerCase().startsWith('udp') ? 'udp' : 'tcp';\n}\n\nasync function getUnixProcessInfo(pid: number): Promise<ProcessInfo> {\n const { stdout } = await execFileAsync('ps', ['-p', String(pid), '-o', 'ppid=,comm=,args=']);\n const line = stdout.trim();\n const [ppidToken, commToken, ...rest] = line.split(/\\s+/);\n const processName = commToken ?? '';\n const command = rest.length > 0 ? rest.join(' ') : processName;\n const ppid = Number(ppidToken);\n\n let parentName = '';\n if (isValidPid(ppid)) {\n try {\n const { stdout: parentStdout } = await execFileAsync('ps', ['-p', String(ppid), '-o', 'comm=']);\n parentName = parentStdout.trim();\n } catch {\n parentName = '';\n }\n }\n\n return { pid, processName, command, parentName };\n}\n\ninterface WindowsProcessRecord {\n Name?: string;\n CommandLine?: string;\n ParentProcessId?: number;\n}\n\nasync function queryWindowsProcess(pid: number): Promise<WindowsProcessRecord | undefined> {\n const script = `Get-CimInstance Win32_Process -Filter \"ProcessId=${pid}\" | Select-Object Name,CommandLine,ParentProcessId | ConvertTo-Json -Compress`;\n const { stdout } = await execFileAsync('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', script]);\n const trimmed = stdout.trim();\n if (!trimmed) return undefined;\n return JSON.parse(trimmed) as WindowsProcessRecord;\n}\n\nasync function getWindowsProcessInfo(pid: number): Promise<ProcessInfo> {\n const record = await queryWindowsProcess(pid);\n if (!record) {\n throw new Error(`No process found for pid ${pid}`);\n }\n\n let parentName = '';\n if (isValidPid(record.ParentProcessId)) {\n try {\n const parentRecord = await queryWindowsProcess(record.ParentProcessId);\n parentName = parentRecord?.Name ?? '';\n } catch {\n parentName = '';\n }\n }\n\n return {\n pid,\n processName: record.Name ?? '',\n command: record.CommandLine ?? record.Name ?? '',\n parentName,\n };\n}\n\nasync function getProcessInfo(pid: number): Promise<ProcessInfo> {\n return process.platform === 'win32' ? getWindowsProcessInfo(pid) : getUnixProcessInfo(pid);\n}\n\nexport async function getListeningPorts(): Promise<PortInfo[]> {\n const connections = await si.networkConnections();\n const listening = connections.filter(\n (conn) => conn.state?.toUpperCase() === 'LISTEN' && isValidPid(conn.pid),\n );\n\n const processInfoByPid = new Map<number, ProcessInfo | null>();\n\n const result: PortInfo[] = [];\n\n for (const conn of listening) {\n const pid = conn.pid;\n\n if (!processInfoByPid.has(pid)) {\n try {\n processInfoByPid.set(pid, await getProcessInfo(pid));\n } catch {\n processInfoByPid.set(pid, null);\n }\n }\n\n const processInfo = processInfoByPid.get(pid);\n if (!processInfo) continue;\n\n const port = Number(conn.localPort);\n if (!Number.isInteger(port)) continue;\n\n result.push({\n port,\n pid,\n protocol: normalizeProtocol(conn.protocol),\n processName: processInfo.processName,\n command: processInfo.command,\n origin: classifyOrigin(processInfo),\n });\n }\n\n const seen = new Set<string>();\n return result.filter((entry) => {\n const key = `${entry.pid}:${entry.port}:${entry.protocol}`;\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n });\n}\n","import type { ClassifyRule, Origin, ProcessInfo } from './types.js';\n\nconst agentHostPattern = /^(code|code-oss|cursor|windsurf)(\\.exe|\\s.*)?$/i;\n\nconst rules: ClassifyRule[] = [\n { namePattern: /claude/i, origin: 'agent' },\n { namePattern: /^(node|python|python3|deno|bun)(\\.exe)?$/i, parentPattern: agentHostPattern, origin: 'agent' },\n { namePattern: /^(bash|zsh|sh|fish|powershell|pwsh|cmd)(\\.exe)?$/i, parentPattern: agentHostPattern, origin: 'agent' },\n];\n\nexport function classifyOrigin(processInfo: ProcessInfo): Origin {\n for (const rule of rules) {\n if (!rule.namePattern.test(processInfo.processName)) continue;\n if (rule.parentPattern && !rule.parentPattern.test(processInfo.parentName)) continue;\n return rule.origin;\n }\n return 'unknown';\n}\n\n// Well-known OS/service process names — used only to declutter a display\n// (e.g. the VS Code sidebar's \"hide system processes\" setting), never to\n// change what getListeningPorts() actually detects. A process spoofing one\n// of these names would be just as invisible to a plain \"netstat\" read, so\n// this is a display convenience, not a security boundary.\nconst systemProcessPattern =\n /^(System|System Idle Process|Registry|smss|csrss|wininit|winlogon|services|lsass|lsaiso|svchost|dwm|fontdrvhost|spoolsv|taskhostw|SearchIndexer|MsMpEng|WmiPrvSE|dllhost|conhost|RuntimeBroker|sihost|ctfmon|explorer|systemd|launchd|kernel_task|init|rpcbind|dbus-daemon|cupsd|mDNSResponder|coreaudiod|WindowServer|loginwindow|SystemUIServer|configd|syslogd|logind|cron|rsyslogd|NetworkManager|polkitd|udevd|avahi-daemon)(\\.exe)?$/i;\n\nexport function isSystemProcess(processName: string): boolean {\n return systemProcessPattern.test(processName);\n}\n","import { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\n\nconst execFileAsync = promisify(execFile);\n\nfunction assertValidPid(pid: number): void {\n if (!Number.isInteger(pid) || pid <= 0) {\n throw new Error(`Invalid pid: ${pid}`);\n }\n}\n\nexport async function killProcess(pid: number): Promise<void> {\n assertValidPid(pid);\n\n if (process.platform === 'win32') {\n await execFileAsync('taskkill', ['/PID', String(pid), '/F']);\n return;\n }\n\n process.kill(pid);\n}\n"],"mappings":"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","names":["import_node_child_process","import_node_util","execFileAsync"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/detect.ts","../src/classify.ts","../src/kill.ts"],"sourcesContent":["export { getListeningPorts } from './detect.js';\nexport { killProcess } from './kill.js';\nexport { classifyOrigin, isSystemProcess } from './classify.js';\nexport type { PortInfo, ProcessInfo, Protocol, Origin, ClassifyRule } from './types.js';\n","import { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport * as si from 'systeminformation';\nimport { classifyOrigin } from './classify.js';\nimport type { PortInfo, Protocol, ProcessInfo } from './types.js';\n\nconst execFileAsync = promisify(execFile);\n\n// Full process tables can be a few hundred KB of JSON/text on a busy\n// machine — well past Node's 1MB default maxBuffer for exec/execFile.\nconst PROCESS_TABLE_MAX_BUFFER = 10 * 1024 * 1024;\n\nfunction isValidPid(pid: unknown): pid is number {\n return typeof pid === 'number' && Number.isInteger(pid) && pid > 0;\n}\n\nfunction normalizeProtocol(raw: string): Protocol {\n return raw.toLowerCase().startsWith('udp') ? 'udp' : 'tcp';\n}\n\ninterface UnixProcessRecord {\n ppid: number;\n comm: string;\n args: string;\n}\n\n// One \"ps\" call for the whole process table, instead of one call per pid\n// (plus one more per parent pid) — the earlier per-pid version re-shelled\n// out for every unique pid on every refresh, which is the dominant cost\n// on a machine with many listening ports.\nasync function queryAllUnixProcesses(): Promise<Map<number, UnixProcessRecord>> {\n const { stdout } = await execFileAsync('ps', ['-A', '-o', 'pid=,ppid=,comm=,args='], {\n maxBuffer: PROCESS_TABLE_MAX_BUFFER,\n });\n\n const table = new Map<number, UnixProcessRecord>();\n for (const line of stdout.split('\\n')) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n\n const [pidToken, ppidToken, commToken, ...rest] = trimmed.split(/\\s+/);\n const pid = Number(pidToken);\n if (!isValidPid(pid)) continue;\n\n const comm = commToken ?? '';\n table.set(pid, {\n ppid: Number(ppidToken),\n comm,\n args: rest.length > 0 ? rest.join(' ') : comm,\n });\n }\n return table;\n}\n\nfunction resolveUnixProcessInfo(pid: number, table: Map<number, UnixProcessRecord>): ProcessInfo {\n const entry = table.get(pid);\n if (!entry) {\n throw new Error(`No process found for pid ${pid}`);\n }\n\n const parentEntry = isValidPid(entry.ppid) ? table.get(entry.ppid) : undefined;\n\n return {\n pid,\n processName: entry.comm,\n command: entry.args,\n parentName: parentEntry?.comm ?? '',\n };\n}\n\ninterface WindowsProcessRecord {\n ProcessId?: number;\n Name?: string;\n CommandLine?: string;\n ParentProcessId?: number;\n}\n\n// Same batching principle as the Unix path: one Get-CimInstance call for\n// every process, instead of a fresh powershell.exe spawn per pid (and\n// another per parent pid) — powershell.exe's own startup overhead, not\n// the WMI query itself, was what made refreshes slow with many ports open.\nasync function queryAllWindowsProcesses(): Promise<Map<number, WindowsProcessRecord>> {\n const script =\n 'Get-CimInstance Win32_Process | ' +\n 'Select-Object ProcessId,Name,CommandLine,ParentProcessId | ' +\n 'ConvertTo-Json -Compress';\n const { stdout } = await execFileAsync('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', script], {\n maxBuffer: PROCESS_TABLE_MAX_BUFFER,\n });\n\n const trimmed = stdout.trim();\n if (!trimmed) return new Map();\n\n const parsed: unknown = JSON.parse(trimmed);\n // ConvertTo-Json emits a bare object instead of a one-element array\n // when the pipeline only produced a single result.\n const records = (Array.isArray(parsed) ? parsed : [parsed]) as WindowsProcessRecord[];\n\n const table = new Map<number, WindowsProcessRecord>();\n for (const record of records) {\n if (isValidPid(record.ProcessId)) {\n table.set(record.ProcessId, record);\n }\n }\n return table;\n}\n\nfunction resolveWindowsProcessInfo(pid: number, table: Map<number, WindowsProcessRecord>): ProcessInfo {\n const record = table.get(pid);\n if (!record) {\n throw new Error(`No process found for pid ${pid}`);\n }\n\n const parentRecord = isValidPid(record.ParentProcessId) ? table.get(record.ParentProcessId) : undefined;\n\n return {\n pid,\n processName: record.Name ?? '',\n command: record.CommandLine ?? record.Name ?? '',\n parentName: parentRecord?.Name ?? '',\n };\n}\n\nexport async function getListeningPorts(): Promise<PortInfo[]> {\n const connections = await si.networkConnections();\n const listening = connections.filter(\n (conn) => conn.state?.toUpperCase() === 'LISTEN' && isValidPid(conn.pid),\n );\n\n if (listening.length === 0) {\n return [];\n }\n\n const isWindows = process.platform === 'win32';\n const processTable = isWindows ? await queryAllWindowsProcesses() : await queryAllUnixProcesses();\n\n const result: PortInfo[] = [];\n\n for (const conn of listening) {\n const pid = conn.pid;\n\n let processInfo: ProcessInfo;\n try {\n processInfo = isWindows\n ? resolveWindowsProcessInfo(pid, processTable as Map<number, WindowsProcessRecord>)\n : resolveUnixProcessInfo(pid, processTable as Map<number, UnixProcessRecord>);\n } catch {\n // Process exited between the port scan and building the process\n // table — it isn't meaningfully \"listening\" anymore, so drop it\n // rather than surface it as unknown.\n continue;\n }\n\n const port = Number(conn.localPort);\n if (!Number.isInteger(port)) continue;\n\n result.push({\n port,\n pid,\n protocol: normalizeProtocol(conn.protocol),\n processName: processInfo.processName,\n command: processInfo.command,\n origin: classifyOrigin(processInfo),\n });\n }\n\n const seen = new Set<string>();\n return result.filter((entry) => {\n const key = `${entry.pid}:${entry.port}:${entry.protocol}`;\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n });\n}\n","import type { ClassifyRule, Origin, ProcessInfo } from './types.js';\n\nconst agentHostPattern = /^(code|code-oss|cursor|windsurf)(\\.exe|\\s.*)?$/i;\n\nconst rules: ClassifyRule[] = [\n { namePattern: /claude/i, origin: 'agent' },\n { namePattern: /^(node|python|python3|deno|bun)(\\.exe)?$/i, parentPattern: agentHostPattern, origin: 'agent' },\n { namePattern: /^(bash|zsh|sh|fish|powershell|pwsh|cmd)(\\.exe)?$/i, parentPattern: agentHostPattern, origin: 'agent' },\n];\n\nexport function classifyOrigin(processInfo: ProcessInfo): Origin {\n for (const rule of rules) {\n if (!rule.namePattern.test(processInfo.processName)) continue;\n if (rule.parentPattern && !rule.parentPattern.test(processInfo.parentName)) continue;\n return rule.origin;\n }\n return 'unknown';\n}\n\n// Well-known OS/service process names — used only to declutter a display\n// (e.g. the VS Code sidebar's \"hide system processes\" setting), never to\n// change what getListeningPorts() actually detects. A process spoofing one\n// of these names would be just as invisible to a plain \"netstat\" read, so\n// this is a display convenience, not a security boundary.\nconst systemProcessPattern =\n /^(System|System Idle Process|Registry|smss|csrss|wininit|winlogon|services|lsass|lsaiso|svchost|dwm|fontdrvhost|spoolsv|taskhostw|SearchIndexer|MsMpEng|WmiPrvSE|dllhost|conhost|RuntimeBroker|sihost|ctfmon|explorer|systemd|launchd|kernel_task|init|rpcbind|dbus-daemon|cupsd|mDNSResponder|coreaudiod|WindowServer|loginwindow|SystemUIServer|configd|syslogd|logind|cron|rsyslogd|NetworkManager|polkitd|udevd|avahi-daemon)(\\.exe)?$/i;\n\nexport function isSystemProcess(processName: string): boolean {\n return systemProcessPattern.test(processName);\n}\n","import { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\n\nconst execFileAsync = promisify(execFile);\n\nfunction assertValidPid(pid: number): void {\n if (!Number.isInteger(pid) || pid <= 0) {\n throw new Error(`Invalid pid: ${pid}`);\n }\n}\n\nexport async function killProcess(pid: number): Promise<void> {\n assertValidPid(pid);\n\n if (process.platform === 'win32') {\n await execFileAsync('taskkill', ['/PID', String(pid), '/F']);\n return;\n }\n\n process.kill(pid);\n}\n"],"mappings":"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","names":["import_node_child_process","import_node_util","execFileAsync"]}
|
package/dist/index.js
CHANGED
|
@@ -25,79 +25,95 @@ function isSystemProcess(processName) {
|
|
|
25
25
|
|
|
26
26
|
// src/detect.ts
|
|
27
27
|
var execFileAsync = promisify(execFile);
|
|
28
|
+
var PROCESS_TABLE_MAX_BUFFER = 10 * 1024 * 1024;
|
|
28
29
|
function isValidPid(pid) {
|
|
29
30
|
return typeof pid === "number" && Number.isInteger(pid) && pid > 0;
|
|
30
31
|
}
|
|
31
32
|
function normalizeProtocol(raw) {
|
|
32
33
|
return raw.toLowerCase().startsWith("udp") ? "udp" : "tcp";
|
|
33
34
|
}
|
|
34
|
-
async function
|
|
35
|
-
const { stdout } = await execFileAsync("ps", ["-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
35
|
+
async function queryAllUnixProcesses() {
|
|
36
|
+
const { stdout } = await execFileAsync("ps", ["-A", "-o", "pid=,ppid=,comm=,args="], {
|
|
37
|
+
maxBuffer: PROCESS_TABLE_MAX_BUFFER
|
|
38
|
+
});
|
|
39
|
+
const table = /* @__PURE__ */ new Map();
|
|
40
|
+
for (const line of stdout.split("\n")) {
|
|
41
|
+
const trimmed = line.trim();
|
|
42
|
+
if (!trimmed) continue;
|
|
43
|
+
const [pidToken, ppidToken, commToken, ...rest] = trimmed.split(/\s+/);
|
|
44
|
+
const pid = Number(pidToken);
|
|
45
|
+
if (!isValidPid(pid)) continue;
|
|
46
|
+
const comm = commToken ?? "";
|
|
47
|
+
table.set(pid, {
|
|
48
|
+
ppid: Number(ppidToken),
|
|
49
|
+
comm,
|
|
50
|
+
args: rest.length > 0 ? rest.join(" ") : comm
|
|
51
|
+
});
|
|
49
52
|
}
|
|
50
|
-
return
|
|
53
|
+
return table;
|
|
54
|
+
}
|
|
55
|
+
function resolveUnixProcessInfo(pid, table) {
|
|
56
|
+
const entry = table.get(pid);
|
|
57
|
+
if (!entry) {
|
|
58
|
+
throw new Error(`No process found for pid ${pid}`);
|
|
59
|
+
}
|
|
60
|
+
const parentEntry = isValidPid(entry.ppid) ? table.get(entry.ppid) : void 0;
|
|
61
|
+
return {
|
|
62
|
+
pid,
|
|
63
|
+
processName: entry.comm,
|
|
64
|
+
command: entry.args,
|
|
65
|
+
parentName: parentEntry?.comm ?? ""
|
|
66
|
+
};
|
|
51
67
|
}
|
|
52
|
-
async function
|
|
53
|
-
const script =
|
|
54
|
-
const { stdout } = await execFileAsync("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", script]
|
|
68
|
+
async function queryAllWindowsProcesses() {
|
|
69
|
+
const script = "Get-CimInstance Win32_Process | Select-Object ProcessId,Name,CommandLine,ParentProcessId | ConvertTo-Json -Compress";
|
|
70
|
+
const { stdout } = await execFileAsync("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", script], {
|
|
71
|
+
maxBuffer: PROCESS_TABLE_MAX_BUFFER
|
|
72
|
+
});
|
|
55
73
|
const trimmed = stdout.trim();
|
|
56
|
-
if (!trimmed) return
|
|
57
|
-
|
|
74
|
+
if (!trimmed) return /* @__PURE__ */ new Map();
|
|
75
|
+
const parsed = JSON.parse(trimmed);
|
|
76
|
+
const records = Array.isArray(parsed) ? parsed : [parsed];
|
|
77
|
+
const table = /* @__PURE__ */ new Map();
|
|
78
|
+
for (const record of records) {
|
|
79
|
+
if (isValidPid(record.ProcessId)) {
|
|
80
|
+
table.set(record.ProcessId, record);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return table;
|
|
58
84
|
}
|
|
59
|
-
|
|
60
|
-
const record =
|
|
85
|
+
function resolveWindowsProcessInfo(pid, table) {
|
|
86
|
+
const record = table.get(pid);
|
|
61
87
|
if (!record) {
|
|
62
88
|
throw new Error(`No process found for pid ${pid}`);
|
|
63
89
|
}
|
|
64
|
-
|
|
65
|
-
if (isValidPid(record.ParentProcessId)) {
|
|
66
|
-
try {
|
|
67
|
-
const parentRecord = await queryWindowsProcess(record.ParentProcessId);
|
|
68
|
-
parentName = parentRecord?.Name ?? "";
|
|
69
|
-
} catch {
|
|
70
|
-
parentName = "";
|
|
71
|
-
}
|
|
72
|
-
}
|
|
90
|
+
const parentRecord = isValidPid(record.ParentProcessId) ? table.get(record.ParentProcessId) : void 0;
|
|
73
91
|
return {
|
|
74
92
|
pid,
|
|
75
93
|
processName: record.Name ?? "",
|
|
76
94
|
command: record.CommandLine ?? record.Name ?? "",
|
|
77
|
-
parentName
|
|
95
|
+
parentName: parentRecord?.Name ?? ""
|
|
78
96
|
};
|
|
79
97
|
}
|
|
80
|
-
async function getProcessInfo(pid) {
|
|
81
|
-
return process.platform === "win32" ? getWindowsProcessInfo(pid) : getUnixProcessInfo(pid);
|
|
82
|
-
}
|
|
83
98
|
async function getListeningPorts() {
|
|
84
99
|
const connections = await si.networkConnections();
|
|
85
100
|
const listening = connections.filter(
|
|
86
101
|
(conn) => conn.state?.toUpperCase() === "LISTEN" && isValidPid(conn.pid)
|
|
87
102
|
);
|
|
88
|
-
|
|
103
|
+
if (listening.length === 0) {
|
|
104
|
+
return [];
|
|
105
|
+
}
|
|
106
|
+
const isWindows = process.platform === "win32";
|
|
107
|
+
const processTable = isWindows ? await queryAllWindowsProcesses() : await queryAllUnixProcesses();
|
|
89
108
|
const result = [];
|
|
90
109
|
for (const conn of listening) {
|
|
91
110
|
const pid = conn.pid;
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
111
|
+
let processInfo;
|
|
112
|
+
try {
|
|
113
|
+
processInfo = isWindows ? resolveWindowsProcessInfo(pid, processTable) : resolveUnixProcessInfo(pid, processTable);
|
|
114
|
+
} catch {
|
|
115
|
+
continue;
|
|
98
116
|
}
|
|
99
|
-
const processInfo = processInfoByPid.get(pid);
|
|
100
|
-
if (!processInfo) continue;
|
|
101
117
|
const port = Number(conn.localPort);
|
|
102
118
|
if (!Number.isInteger(port)) continue;
|
|
103
119
|
result.push({
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/detect.ts","../src/classify.ts","../src/kill.ts"],"sourcesContent":["import { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport * as si from 'systeminformation';\nimport { classifyOrigin } from './classify.js';\nimport type { PortInfo, Protocol, ProcessInfo } from './types.js';\n\nconst execFileAsync = promisify(execFile);\n\nfunction isValidPid(pid: unknown): pid is number {\n return typeof pid === 'number' && Number.isInteger(pid) && pid > 0;\n}\n\nfunction normalizeProtocol(raw: string): Protocol {\n return raw.toLowerCase().startsWith('udp') ? 'udp' : 'tcp';\n}\n\nasync function getUnixProcessInfo(pid: number): Promise<ProcessInfo> {\n const { stdout } = await execFileAsync('ps', ['-p', String(pid), '-o', 'ppid=,comm=,args=']);\n const line = stdout.trim();\n const [ppidToken, commToken, ...rest] = line.split(/\\s+/);\n const processName = commToken ?? '';\n const command = rest.length > 0 ? rest.join(' ') : processName;\n const ppid = Number(ppidToken);\n\n let parentName = '';\n if (isValidPid(ppid)) {\n try {\n const { stdout: parentStdout } = await execFileAsync('ps', ['-p', String(ppid), '-o', 'comm=']);\n parentName = parentStdout.trim();\n } catch {\n parentName = '';\n }\n }\n\n return { pid, processName, command, parentName };\n}\n\ninterface WindowsProcessRecord {\n Name?: string;\n CommandLine?: string;\n ParentProcessId?: number;\n}\n\nasync function queryWindowsProcess(pid: number): Promise<WindowsProcessRecord | undefined> {\n const script = `Get-CimInstance Win32_Process -Filter \"ProcessId=${pid}\" | Select-Object Name,CommandLine,ParentProcessId | ConvertTo-Json -Compress`;\n const { stdout } = await execFileAsync('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', script]);\n const trimmed = stdout.trim();\n if (!trimmed) return undefined;\n return JSON.parse(trimmed) as WindowsProcessRecord;\n}\n\nasync function getWindowsProcessInfo(pid: number): Promise<ProcessInfo> {\n const record = await queryWindowsProcess(pid);\n if (!record) {\n throw new Error(`No process found for pid ${pid}`);\n }\n\n let parentName = '';\n if (isValidPid(record.ParentProcessId)) {\n try {\n const parentRecord = await queryWindowsProcess(record.ParentProcessId);\n parentName = parentRecord?.Name ?? '';\n } catch {\n parentName = '';\n }\n }\n\n return {\n pid,\n processName: record.Name ?? '',\n command: record.CommandLine ?? record.Name ?? '',\n parentName,\n };\n}\n\nasync function getProcessInfo(pid: number): Promise<ProcessInfo> {\n return process.platform === 'win32' ? getWindowsProcessInfo(pid) : getUnixProcessInfo(pid);\n}\n\nexport async function getListeningPorts(): Promise<PortInfo[]> {\n const connections = await si.networkConnections();\n const listening = connections.filter(\n (conn) => conn.state?.toUpperCase() === 'LISTEN' && isValidPid(conn.pid),\n );\n\n const processInfoByPid = new Map<number, ProcessInfo | null>();\n\n const result: PortInfo[] = [];\n\n for (const conn of listening) {\n const pid = conn.pid;\n\n if (!processInfoByPid.has(pid)) {\n try {\n processInfoByPid.set(pid, await getProcessInfo(pid));\n } catch {\n processInfoByPid.set(pid, null);\n }\n }\n\n const processInfo = processInfoByPid.get(pid);\n if (!processInfo) continue;\n\n const port = Number(conn.localPort);\n if (!Number.isInteger(port)) continue;\n\n result.push({\n port,\n pid,\n protocol: normalizeProtocol(conn.protocol),\n processName: processInfo.processName,\n command: processInfo.command,\n origin: classifyOrigin(processInfo),\n });\n }\n\n const seen = new Set<string>();\n return result.filter((entry) => {\n const key = `${entry.pid}:${entry.port}:${entry.protocol}`;\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n });\n}\n","import type { ClassifyRule, Origin, ProcessInfo } from './types.js';\n\nconst agentHostPattern = /^(code|code-oss|cursor|windsurf)(\\.exe|\\s.*)?$/i;\n\nconst rules: ClassifyRule[] = [\n { namePattern: /claude/i, origin: 'agent' },\n { namePattern: /^(node|python|python3|deno|bun)(\\.exe)?$/i, parentPattern: agentHostPattern, origin: 'agent' },\n { namePattern: /^(bash|zsh|sh|fish|powershell|pwsh|cmd)(\\.exe)?$/i, parentPattern: agentHostPattern, origin: 'agent' },\n];\n\nexport function classifyOrigin(processInfo: ProcessInfo): Origin {\n for (const rule of rules) {\n if (!rule.namePattern.test(processInfo.processName)) continue;\n if (rule.parentPattern && !rule.parentPattern.test(processInfo.parentName)) continue;\n return rule.origin;\n }\n return 'unknown';\n}\n\n// Well-known OS/service process names — used only to declutter a display\n// (e.g. the VS Code sidebar's \"hide system processes\" setting), never to\n// change what getListeningPorts() actually detects. A process spoofing one\n// of these names would be just as invisible to a plain \"netstat\" read, so\n// this is a display convenience, not a security boundary.\nconst systemProcessPattern =\n /^(System|System Idle Process|Registry|smss|csrss|wininit|winlogon|services|lsass|lsaiso|svchost|dwm|fontdrvhost|spoolsv|taskhostw|SearchIndexer|MsMpEng|WmiPrvSE|dllhost|conhost|RuntimeBroker|sihost|ctfmon|explorer|systemd|launchd|kernel_task|init|rpcbind|dbus-daemon|cupsd|mDNSResponder|coreaudiod|WindowServer|loginwindow|SystemUIServer|configd|syslogd|logind|cron|rsyslogd|NetworkManager|polkitd|udevd|avahi-daemon)(\\.exe)?$/i;\n\nexport function isSystemProcess(processName: string): boolean {\n return systemProcessPattern.test(processName);\n}\n","import { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\n\nconst execFileAsync = promisify(execFile);\n\nfunction assertValidPid(pid: number): void {\n if (!Number.isInteger(pid) || pid <= 0) {\n throw new Error(`Invalid pid: ${pid}`);\n }\n}\n\nexport async function killProcess(pid: number): Promise<void> {\n assertValidPid(pid);\n\n if (process.platform === 'win32') {\n await execFileAsync('taskkill', ['/PID', String(pid), '/F']);\n return;\n }\n\n process.kill(pid);\n}\n"],"mappings":";AAAA,SAAS,gBAAgB;AACzB,SAAS,iBAAiB;AAC1B,YAAY,QAAQ;;;ACApB,IAAM,mBAAmB;AAEzB,IAAM,QAAwB;AAAA,EAC5B,EAAE,aAAa,WAAW,QAAQ,QAAQ;AAAA,EAC1C,EAAE,aAAa,6CAA6C,eAAe,kBAAkB,QAAQ,QAAQ;AAAA,EAC7G,EAAE,aAAa,qDAAqD,eAAe,kBAAkB,QAAQ,QAAQ;AACvH;AAEO,SAAS,eAAe,aAAkC;AAC/D,aAAW,QAAQ,OAAO;AACxB,QAAI,CAAC,KAAK,YAAY,KAAK,YAAY,WAAW,EAAG;AACrD,QAAI,KAAK,iBAAiB,CAAC,KAAK,cAAc,KAAK,YAAY,UAAU,EAAG;AAC5E,WAAO,KAAK;AAAA,EACd;AACA,SAAO;AACT;AAOA,IAAM,uBACJ;AAEK,SAAS,gBAAgB,aAA8B;AAC5D,SAAO,qBAAqB,KAAK,WAAW;AAC9C;;;ADvBA,IAAM,gBAAgB,UAAU,QAAQ;AAExC,SAAS,WAAW,KAA6B;AAC/C,SAAO,OAAO,QAAQ,YAAY,OAAO,UAAU,GAAG,KAAK,MAAM;AACnE;AAEA,SAAS,kBAAkB,KAAuB;AAChD,SAAO,IAAI,YAAY,EAAE,WAAW,KAAK,IAAI,QAAQ;AACvD;AAEA,eAAe,mBAAmB,KAAmC;AACnE,QAAM,EAAE,OAAO,IAAI,MAAM,cAAc,MAAM,CAAC,MAAM,OAAO,GAAG,GAAG,MAAM,mBAAmB,CAAC;AAC3F,QAAM,OAAO,OAAO,KAAK;AACzB,QAAM,CAAC,WAAW,WAAW,GAAG,IAAI,IAAI,KAAK,MAAM,KAAK;AACxD,QAAM,cAAc,aAAa;AACjC,QAAM,UAAU,KAAK,SAAS,IAAI,KAAK,KAAK,GAAG,IAAI;AACnD,QAAM,OAAO,OAAO,SAAS;AAE7B,MAAI,aAAa;AACjB,MAAI,WAAW,IAAI,GAAG;AACpB,QAAI;AACF,YAAM,EAAE,QAAQ,aAAa,IAAI,MAAM,cAAc,MAAM,CAAC,MAAM,OAAO,IAAI,GAAG,MAAM,OAAO,CAAC;AAC9F,mBAAa,aAAa,KAAK;AAAA,IACjC,QAAQ;AACN,mBAAa;AAAA,IACf;AAAA,EACF;AAEA,SAAO,EAAE,KAAK,aAAa,SAAS,WAAW;AACjD;AAQA,eAAe,oBAAoB,KAAwD;AACzF,QAAM,SAAS,oDAAoD,GAAG;AACtE,QAAM,EAAE,OAAO,IAAI,MAAM,cAAc,kBAAkB,CAAC,cAAc,mBAAmB,YAAY,MAAM,CAAC;AAC9G,QAAM,UAAU,OAAO,KAAK;AAC5B,MAAI,CAAC,QAAS,QAAO;AACrB,SAAO,KAAK,MAAM,OAAO;AAC3B;AAEA,eAAe,sBAAsB,KAAmC;AACtE,QAAM,SAAS,MAAM,oBAAoB,GAAG;AAC5C,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI,MAAM,4BAA4B,GAAG,EAAE;AAAA,EACnD;AAEA,MAAI,aAAa;AACjB,MAAI,WAAW,OAAO,eAAe,GAAG;AACtC,QAAI;AACF,YAAM,eAAe,MAAM,oBAAoB,OAAO,eAAe;AACrE,mBAAa,cAAc,QAAQ;AAAA,IACrC,QAAQ;AACN,mBAAa;AAAA,IACf;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA,aAAa,OAAO,QAAQ;AAAA,IAC5B,SAAS,OAAO,eAAe,OAAO,QAAQ;AAAA,IAC9C;AAAA,EACF;AACF;AAEA,eAAe,eAAe,KAAmC;AAC/D,SAAO,QAAQ,aAAa,UAAU,sBAAsB,GAAG,IAAI,mBAAmB,GAAG;AAC3F;AAEA,eAAsB,oBAAyC;AAC7D,QAAM,cAAc,MAAS,sBAAmB;AAChD,QAAM,YAAY,YAAY;AAAA,IAC5B,CAAC,SAAS,KAAK,OAAO,YAAY,MAAM,YAAY,WAAW,KAAK,GAAG;AAAA,EACzE;AAEA,QAAM,mBAAmB,oBAAI,IAAgC;AAE7D,QAAM,SAAqB,CAAC;AAE5B,aAAW,QAAQ,WAAW;AAC5B,UAAM,MAAM,KAAK;AAEjB,QAAI,CAAC,iBAAiB,IAAI,GAAG,GAAG;AAC9B,UAAI;AACF,yBAAiB,IAAI,KAAK,MAAM,eAAe,GAAG,CAAC;AAAA,MACrD,QAAQ;AACN,yBAAiB,IAAI,KAAK,IAAI;AAAA,MAChC;AAAA,IACF;AAEA,UAAM,cAAc,iBAAiB,IAAI,GAAG;AAC5C,QAAI,CAAC,YAAa;AAElB,UAAM,OAAO,OAAO,KAAK,SAAS;AAClC,QAAI,CAAC,OAAO,UAAU,IAAI,EAAG;AAE7B,WAAO,KAAK;AAAA,MACV;AAAA,MACA;AAAA,MACA,UAAU,kBAAkB,KAAK,QAAQ;AAAA,MACzC,aAAa,YAAY;AAAA,MACzB,SAAS,YAAY;AAAA,MACrB,QAAQ,eAAe,WAAW;AAAA,IACpC,CAAC;AAAA,EACH;AAEA,QAAM,OAAO,oBAAI,IAAY;AAC7B,SAAO,OAAO,OAAO,CAAC,UAAU;AAC9B,UAAM,MAAM,GAAG,MAAM,GAAG,IAAI,MAAM,IAAI,IAAI,MAAM,QAAQ;AACxD,QAAI,KAAK,IAAI,GAAG,EAAG,QAAO;AAC1B,SAAK,IAAI,GAAG;AACZ,WAAO;AAAA,EACT,CAAC;AACH;;;AE3HA,SAAS,YAAAA,iBAAgB;AACzB,SAAS,aAAAC,kBAAiB;AAE1B,IAAMC,iBAAgBD,WAAUD,SAAQ;AAExC,SAAS,eAAe,KAAmB;AACzC,MAAI,CAAC,OAAO,UAAU,GAAG,KAAK,OAAO,GAAG;AACtC,UAAM,IAAI,MAAM,gBAAgB,GAAG,EAAE;AAAA,EACvC;AACF;AAEA,eAAsB,YAAY,KAA4B;AAC5D,iBAAe,GAAG;AAElB,MAAI,QAAQ,aAAa,SAAS;AAChC,UAAME,eAAc,YAAY,CAAC,QAAQ,OAAO,GAAG,GAAG,IAAI,CAAC;AAC3D;AAAA,EACF;AAEA,UAAQ,KAAK,GAAG;AAClB;","names":["execFile","promisify","execFileAsync"]}
|
|
1
|
+
{"version":3,"sources":["../src/detect.ts","../src/classify.ts","../src/kill.ts"],"sourcesContent":["import { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport * as si from 'systeminformation';\nimport { classifyOrigin } from './classify.js';\nimport type { PortInfo, Protocol, ProcessInfo } from './types.js';\n\nconst execFileAsync = promisify(execFile);\n\n// Full process tables can be a few hundred KB of JSON/text on a busy\n// machine — well past Node's 1MB default maxBuffer for exec/execFile.\nconst PROCESS_TABLE_MAX_BUFFER = 10 * 1024 * 1024;\n\nfunction isValidPid(pid: unknown): pid is number {\n return typeof pid === 'number' && Number.isInteger(pid) && pid > 0;\n}\n\nfunction normalizeProtocol(raw: string): Protocol {\n return raw.toLowerCase().startsWith('udp') ? 'udp' : 'tcp';\n}\n\ninterface UnixProcessRecord {\n ppid: number;\n comm: string;\n args: string;\n}\n\n// One \"ps\" call for the whole process table, instead of one call per pid\n// (plus one more per parent pid) — the earlier per-pid version re-shelled\n// out for every unique pid on every refresh, which is the dominant cost\n// on a machine with many listening ports.\nasync function queryAllUnixProcesses(): Promise<Map<number, UnixProcessRecord>> {\n const { stdout } = await execFileAsync('ps', ['-A', '-o', 'pid=,ppid=,comm=,args='], {\n maxBuffer: PROCESS_TABLE_MAX_BUFFER,\n });\n\n const table = new Map<number, UnixProcessRecord>();\n for (const line of stdout.split('\\n')) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n\n const [pidToken, ppidToken, commToken, ...rest] = trimmed.split(/\\s+/);\n const pid = Number(pidToken);\n if (!isValidPid(pid)) continue;\n\n const comm = commToken ?? '';\n table.set(pid, {\n ppid: Number(ppidToken),\n comm,\n args: rest.length > 0 ? rest.join(' ') : comm,\n });\n }\n return table;\n}\n\nfunction resolveUnixProcessInfo(pid: number, table: Map<number, UnixProcessRecord>): ProcessInfo {\n const entry = table.get(pid);\n if (!entry) {\n throw new Error(`No process found for pid ${pid}`);\n }\n\n const parentEntry = isValidPid(entry.ppid) ? table.get(entry.ppid) : undefined;\n\n return {\n pid,\n processName: entry.comm,\n command: entry.args,\n parentName: parentEntry?.comm ?? '',\n };\n}\n\ninterface WindowsProcessRecord {\n ProcessId?: number;\n Name?: string;\n CommandLine?: string;\n ParentProcessId?: number;\n}\n\n// Same batching principle as the Unix path: one Get-CimInstance call for\n// every process, instead of a fresh powershell.exe spawn per pid (and\n// another per parent pid) — powershell.exe's own startup overhead, not\n// the WMI query itself, was what made refreshes slow with many ports open.\nasync function queryAllWindowsProcesses(): Promise<Map<number, WindowsProcessRecord>> {\n const script =\n 'Get-CimInstance Win32_Process | ' +\n 'Select-Object ProcessId,Name,CommandLine,ParentProcessId | ' +\n 'ConvertTo-Json -Compress';\n const { stdout } = await execFileAsync('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', script], {\n maxBuffer: PROCESS_TABLE_MAX_BUFFER,\n });\n\n const trimmed = stdout.trim();\n if (!trimmed) return new Map();\n\n const parsed: unknown = JSON.parse(trimmed);\n // ConvertTo-Json emits a bare object instead of a one-element array\n // when the pipeline only produced a single result.\n const records = (Array.isArray(parsed) ? parsed : [parsed]) as WindowsProcessRecord[];\n\n const table = new Map<number, WindowsProcessRecord>();\n for (const record of records) {\n if (isValidPid(record.ProcessId)) {\n table.set(record.ProcessId, record);\n }\n }\n return table;\n}\n\nfunction resolveWindowsProcessInfo(pid: number, table: Map<number, WindowsProcessRecord>): ProcessInfo {\n const record = table.get(pid);\n if (!record) {\n throw new Error(`No process found for pid ${pid}`);\n }\n\n const parentRecord = isValidPid(record.ParentProcessId) ? table.get(record.ParentProcessId) : undefined;\n\n return {\n pid,\n processName: record.Name ?? '',\n command: record.CommandLine ?? record.Name ?? '',\n parentName: parentRecord?.Name ?? '',\n };\n}\n\nexport async function getListeningPorts(): Promise<PortInfo[]> {\n const connections = await si.networkConnections();\n const listening = connections.filter(\n (conn) => conn.state?.toUpperCase() === 'LISTEN' && isValidPid(conn.pid),\n );\n\n if (listening.length === 0) {\n return [];\n }\n\n const isWindows = process.platform === 'win32';\n const processTable = isWindows ? await queryAllWindowsProcesses() : await queryAllUnixProcesses();\n\n const result: PortInfo[] = [];\n\n for (const conn of listening) {\n const pid = conn.pid;\n\n let processInfo: ProcessInfo;\n try {\n processInfo = isWindows\n ? resolveWindowsProcessInfo(pid, processTable as Map<number, WindowsProcessRecord>)\n : resolveUnixProcessInfo(pid, processTable as Map<number, UnixProcessRecord>);\n } catch {\n // Process exited between the port scan and building the process\n // table — it isn't meaningfully \"listening\" anymore, so drop it\n // rather than surface it as unknown.\n continue;\n }\n\n const port = Number(conn.localPort);\n if (!Number.isInteger(port)) continue;\n\n result.push({\n port,\n pid,\n protocol: normalizeProtocol(conn.protocol),\n processName: processInfo.processName,\n command: processInfo.command,\n origin: classifyOrigin(processInfo),\n });\n }\n\n const seen = new Set<string>();\n return result.filter((entry) => {\n const key = `${entry.pid}:${entry.port}:${entry.protocol}`;\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n });\n}\n","import type { ClassifyRule, Origin, ProcessInfo } from './types.js';\n\nconst agentHostPattern = /^(code|code-oss|cursor|windsurf)(\\.exe|\\s.*)?$/i;\n\nconst rules: ClassifyRule[] = [\n { namePattern: /claude/i, origin: 'agent' },\n { namePattern: /^(node|python|python3|deno|bun)(\\.exe)?$/i, parentPattern: agentHostPattern, origin: 'agent' },\n { namePattern: /^(bash|zsh|sh|fish|powershell|pwsh|cmd)(\\.exe)?$/i, parentPattern: agentHostPattern, origin: 'agent' },\n];\n\nexport function classifyOrigin(processInfo: ProcessInfo): Origin {\n for (const rule of rules) {\n if (!rule.namePattern.test(processInfo.processName)) continue;\n if (rule.parentPattern && !rule.parentPattern.test(processInfo.parentName)) continue;\n return rule.origin;\n }\n return 'unknown';\n}\n\n// Well-known OS/service process names — used only to declutter a display\n// (e.g. the VS Code sidebar's \"hide system processes\" setting), never to\n// change what getListeningPorts() actually detects. A process spoofing one\n// of these names would be just as invisible to a plain \"netstat\" read, so\n// this is a display convenience, not a security boundary.\nconst systemProcessPattern =\n /^(System|System Idle Process|Registry|smss|csrss|wininit|winlogon|services|lsass|lsaiso|svchost|dwm|fontdrvhost|spoolsv|taskhostw|SearchIndexer|MsMpEng|WmiPrvSE|dllhost|conhost|RuntimeBroker|sihost|ctfmon|explorer|systemd|launchd|kernel_task|init|rpcbind|dbus-daemon|cupsd|mDNSResponder|coreaudiod|WindowServer|loginwindow|SystemUIServer|configd|syslogd|logind|cron|rsyslogd|NetworkManager|polkitd|udevd|avahi-daemon)(\\.exe)?$/i;\n\nexport function isSystemProcess(processName: string): boolean {\n return systemProcessPattern.test(processName);\n}\n","import { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\n\nconst execFileAsync = promisify(execFile);\n\nfunction assertValidPid(pid: number): void {\n if (!Number.isInteger(pid) || pid <= 0) {\n throw new Error(`Invalid pid: ${pid}`);\n }\n}\n\nexport async function killProcess(pid: number): Promise<void> {\n assertValidPid(pid);\n\n if (process.platform === 'win32') {\n await execFileAsync('taskkill', ['/PID', String(pid), '/F']);\n return;\n }\n\n process.kill(pid);\n}\n"],"mappings":"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","names":["execFile","promisify","execFileAsync"]}
|