porthawk-core 0.1.5 → 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 +67 -45
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +66 -45
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -32,6 +32,7 @@ var index_exports = {};
|
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
classifyOrigin: () => classifyOrigin,
|
|
34
34
|
getListeningPorts: () => getListeningPorts,
|
|
35
|
+
isSystemProcess: () => isSystemProcess,
|
|
35
36
|
killProcess: () => killProcess
|
|
36
37
|
});
|
|
37
38
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -56,82 +57,102 @@ function classifyOrigin(processInfo) {
|
|
|
56
57
|
}
|
|
57
58
|
return "unknown";
|
|
58
59
|
}
|
|
60
|
+
var systemProcessPattern = /^(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;
|
|
61
|
+
function isSystemProcess(processName) {
|
|
62
|
+
return systemProcessPattern.test(processName);
|
|
63
|
+
}
|
|
59
64
|
|
|
60
65
|
// src/detect.ts
|
|
61
66
|
var execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
|
|
67
|
+
var PROCESS_TABLE_MAX_BUFFER = 10 * 1024 * 1024;
|
|
62
68
|
function isValidPid(pid) {
|
|
63
69
|
return typeof pid === "number" && Number.isInteger(pid) && pid > 0;
|
|
64
70
|
}
|
|
65
71
|
function normalizeProtocol(raw) {
|
|
66
72
|
return raw.toLowerCase().startsWith("udp") ? "udp" : "tcp";
|
|
67
73
|
}
|
|
68
|
-
async function
|
|
69
|
-
const { stdout } = await execFileAsync("ps", ["-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
+
});
|
|
83
91
|
}
|
|
84
|
-
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
|
+
};
|
|
85
106
|
}
|
|
86
|
-
async function
|
|
87
|
-
const script =
|
|
88
|
-
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
|
+
});
|
|
89
112
|
const trimmed = stdout.trim();
|
|
90
|
-
if (!trimmed) return
|
|
91
|
-
|
|
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;
|
|
92
123
|
}
|
|
93
|
-
|
|
94
|
-
const record =
|
|
124
|
+
function resolveWindowsProcessInfo(pid, table) {
|
|
125
|
+
const record = table.get(pid);
|
|
95
126
|
if (!record) {
|
|
96
127
|
throw new Error(`No process found for pid ${pid}`);
|
|
97
128
|
}
|
|
98
|
-
|
|
99
|
-
if (isValidPid(record.ParentProcessId)) {
|
|
100
|
-
try {
|
|
101
|
-
const parentRecord = await queryWindowsProcess(record.ParentProcessId);
|
|
102
|
-
parentName = parentRecord?.Name ?? "";
|
|
103
|
-
} catch {
|
|
104
|
-
parentName = "";
|
|
105
|
-
}
|
|
106
|
-
}
|
|
129
|
+
const parentRecord = isValidPid(record.ParentProcessId) ? table.get(record.ParentProcessId) : void 0;
|
|
107
130
|
return {
|
|
108
131
|
pid,
|
|
109
132
|
processName: record.Name ?? "",
|
|
110
133
|
command: record.CommandLine ?? record.Name ?? "",
|
|
111
|
-
parentName
|
|
134
|
+
parentName: parentRecord?.Name ?? ""
|
|
112
135
|
};
|
|
113
136
|
}
|
|
114
|
-
async function getProcessInfo(pid) {
|
|
115
|
-
return process.platform === "win32" ? getWindowsProcessInfo(pid) : getUnixProcessInfo(pid);
|
|
116
|
-
}
|
|
117
137
|
async function getListeningPorts() {
|
|
118
138
|
const connections = await si.networkConnections();
|
|
119
139
|
const listening = connections.filter(
|
|
120
140
|
(conn) => conn.state?.toUpperCase() === "LISTEN" && isValidPid(conn.pid)
|
|
121
141
|
);
|
|
122
|
-
|
|
142
|
+
if (listening.length === 0) {
|
|
143
|
+
return [];
|
|
144
|
+
}
|
|
145
|
+
const isWindows = process.platform === "win32";
|
|
146
|
+
const processTable = isWindows ? await queryAllWindowsProcesses() : await queryAllUnixProcesses();
|
|
123
147
|
const result = [];
|
|
124
148
|
for (const conn of listening) {
|
|
125
149
|
const pid = conn.pid;
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}
|
|
150
|
+
let processInfo;
|
|
151
|
+
try {
|
|
152
|
+
processInfo = isWindows ? resolveWindowsProcessInfo(pid, processTable) : resolveUnixProcessInfo(pid, processTable);
|
|
153
|
+
} catch {
|
|
154
|
+
continue;
|
|
132
155
|
}
|
|
133
|
-
const processInfo = processInfoByPid.get(pid);
|
|
134
|
-
if (!processInfo) continue;
|
|
135
156
|
const port = Number(conn.localPort);
|
|
136
157
|
if (!Number.isInteger(port)) continue;
|
|
137
158
|
result.push({
|
|
@@ -173,6 +194,7 @@ async function killProcess(pid) {
|
|
|
173
194
|
0 && (module.exports = {
|
|
174
195
|
classifyOrigin,
|
|
175
196
|
getListeningPorts,
|
|
197
|
+
isSystemProcess,
|
|
176
198
|
killProcess
|
|
177
199
|
});
|
|
178
200
|
//# sourceMappingURL=index.cjs.map
|
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 } 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","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.d.cts
CHANGED
|
@@ -25,5 +25,6 @@ declare function getListeningPorts(): Promise<PortInfo[]>;
|
|
|
25
25
|
declare function killProcess(pid: number): Promise<void>;
|
|
26
26
|
|
|
27
27
|
declare function classifyOrigin(processInfo: ProcessInfo): Origin;
|
|
28
|
+
declare function isSystemProcess(processName: string): boolean;
|
|
28
29
|
|
|
29
|
-
export { type ClassifyRule, type Origin, type PortInfo, type ProcessInfo, type Protocol, classifyOrigin, getListeningPorts, killProcess };
|
|
30
|
+
export { type ClassifyRule, type Origin, type PortInfo, type ProcessInfo, type Protocol, classifyOrigin, getListeningPorts, isSystemProcess, killProcess };
|
package/dist/index.d.ts
CHANGED
|
@@ -25,5 +25,6 @@ declare function getListeningPorts(): Promise<PortInfo[]>;
|
|
|
25
25
|
declare function killProcess(pid: number): Promise<void>;
|
|
26
26
|
|
|
27
27
|
declare function classifyOrigin(processInfo: ProcessInfo): Origin;
|
|
28
|
+
declare function isSystemProcess(processName: string): boolean;
|
|
28
29
|
|
|
29
|
-
export { type ClassifyRule, type Origin, type PortInfo, type ProcessInfo, type Protocol, classifyOrigin, getListeningPorts, killProcess };
|
|
30
|
+
export { type ClassifyRule, type Origin, type PortInfo, type ProcessInfo, type Protocol, classifyOrigin, getListeningPorts, isSystemProcess, killProcess };
|
package/dist/index.js
CHANGED
|
@@ -18,82 +18,102 @@ function classifyOrigin(processInfo) {
|
|
|
18
18
|
}
|
|
19
19
|
return "unknown";
|
|
20
20
|
}
|
|
21
|
+
var systemProcessPattern = /^(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;
|
|
22
|
+
function isSystemProcess(processName) {
|
|
23
|
+
return systemProcessPattern.test(processName);
|
|
24
|
+
}
|
|
21
25
|
|
|
22
26
|
// src/detect.ts
|
|
23
27
|
var execFileAsync = promisify(execFile);
|
|
28
|
+
var PROCESS_TABLE_MAX_BUFFER = 10 * 1024 * 1024;
|
|
24
29
|
function isValidPid(pid) {
|
|
25
30
|
return typeof pid === "number" && Number.isInteger(pid) && pid > 0;
|
|
26
31
|
}
|
|
27
32
|
function normalizeProtocol(raw) {
|
|
28
33
|
return raw.toLowerCase().startsWith("udp") ? "udp" : "tcp";
|
|
29
34
|
}
|
|
30
|
-
async function
|
|
31
|
-
const { stdout } = await execFileAsync("ps", ["-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
+
});
|
|
45
52
|
}
|
|
46
|
-
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
|
+
};
|
|
47
67
|
}
|
|
48
|
-
async function
|
|
49
|
-
const script =
|
|
50
|
-
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
|
+
});
|
|
51
73
|
const trimmed = stdout.trim();
|
|
52
|
-
if (!trimmed) return
|
|
53
|
-
|
|
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;
|
|
54
84
|
}
|
|
55
|
-
|
|
56
|
-
const record =
|
|
85
|
+
function resolveWindowsProcessInfo(pid, table) {
|
|
86
|
+
const record = table.get(pid);
|
|
57
87
|
if (!record) {
|
|
58
88
|
throw new Error(`No process found for pid ${pid}`);
|
|
59
89
|
}
|
|
60
|
-
|
|
61
|
-
if (isValidPid(record.ParentProcessId)) {
|
|
62
|
-
try {
|
|
63
|
-
const parentRecord = await queryWindowsProcess(record.ParentProcessId);
|
|
64
|
-
parentName = parentRecord?.Name ?? "";
|
|
65
|
-
} catch {
|
|
66
|
-
parentName = "";
|
|
67
|
-
}
|
|
68
|
-
}
|
|
90
|
+
const parentRecord = isValidPid(record.ParentProcessId) ? table.get(record.ParentProcessId) : void 0;
|
|
69
91
|
return {
|
|
70
92
|
pid,
|
|
71
93
|
processName: record.Name ?? "",
|
|
72
94
|
command: record.CommandLine ?? record.Name ?? "",
|
|
73
|
-
parentName
|
|
95
|
+
parentName: parentRecord?.Name ?? ""
|
|
74
96
|
};
|
|
75
97
|
}
|
|
76
|
-
async function getProcessInfo(pid) {
|
|
77
|
-
return process.platform === "win32" ? getWindowsProcessInfo(pid) : getUnixProcessInfo(pid);
|
|
78
|
-
}
|
|
79
98
|
async function getListeningPorts() {
|
|
80
99
|
const connections = await si.networkConnections();
|
|
81
100
|
const listening = connections.filter(
|
|
82
101
|
(conn) => conn.state?.toUpperCase() === "LISTEN" && isValidPid(conn.pid)
|
|
83
102
|
);
|
|
84
|
-
|
|
103
|
+
if (listening.length === 0) {
|
|
104
|
+
return [];
|
|
105
|
+
}
|
|
106
|
+
const isWindows = process.platform === "win32";
|
|
107
|
+
const processTable = isWindows ? await queryAllWindowsProcesses() : await queryAllUnixProcesses();
|
|
85
108
|
const result = [];
|
|
86
109
|
for (const conn of listening) {
|
|
87
110
|
const pid = conn.pid;
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
111
|
+
let processInfo;
|
|
112
|
+
try {
|
|
113
|
+
processInfo = isWindows ? resolveWindowsProcessInfo(pid, processTable) : resolveUnixProcessInfo(pid, processTable);
|
|
114
|
+
} catch {
|
|
115
|
+
continue;
|
|
94
116
|
}
|
|
95
|
-
const processInfo = processInfoByPid.get(pid);
|
|
96
|
-
if (!processInfo) continue;
|
|
97
117
|
const port = Number(conn.localPort);
|
|
98
118
|
if (!Number.isInteger(port)) continue;
|
|
99
119
|
result.push({
|
|
@@ -134,6 +154,7 @@ async function killProcess(pid) {
|
|
|
134
154
|
export {
|
|
135
155
|
classifyOrigin,
|
|
136
156
|
getListeningPorts,
|
|
157
|
+
isSystemProcess,
|
|
137
158
|
killProcess
|
|
138
159
|
};
|
|
139
160
|
//# sourceMappingURL=index.js.map
|
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","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;;;ADXA,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"]}
|