claude-hook-notify 1.4.0 → 1.4.1
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/package.json
CHANGED
package/src/activate.js
CHANGED
|
@@ -115,30 +115,21 @@ function detectMac() {
|
|
|
115
115
|
* Windows: 优先环境变量,回退单次 PowerShell 批量查询
|
|
116
116
|
*/
|
|
117
117
|
function detectWindows() {
|
|
118
|
-
// 1.
|
|
119
|
-
if (process.env.WT_SESSION) {
|
|
120
|
-
// Windows Terminal 设置了 WT_SESSION
|
|
121
|
-
const info = findProcessByName("WindowsTerminal");
|
|
122
|
-
if (info) return info;
|
|
123
|
-
}
|
|
118
|
+
// 1. 环境变量快速检测(仅保留精确 PID 的路径)
|
|
124
119
|
if (process.env.VSCODE_PID) {
|
|
125
120
|
const pid = parseInt(process.env.VSCODE_PID, 10);
|
|
126
121
|
if (pid > 0) return { name: "Code", pid, processName: "code.exe" };
|
|
127
122
|
}
|
|
128
|
-
if (process.env.TERM_PROGRAM === "vscode") {
|
|
129
|
-
const info = findProcessByName("Code");
|
|
130
|
-
if (info) return info;
|
|
131
|
-
}
|
|
132
123
|
|
|
133
|
-
// 2.
|
|
124
|
+
// 2. WMI 遍历进程树(OS 级 ParentProcessId 始终完整,不受 spawn 方式影响)
|
|
134
125
|
try {
|
|
135
126
|
const script = [
|
|
136
|
-
"$cpid=" + process.
|
|
137
|
-
"for($i=0;$i -lt
|
|
138
|
-
" $
|
|
139
|
-
" if(-not $
|
|
140
|
-
" Write-Output \"$($
|
|
141
|
-
" $cpid
|
|
127
|
+
"$cpid=" + process.pid,
|
|
128
|
+
"for($i=0;$i -lt 30 -and $cpid -gt 0;$i++){",
|
|
129
|
+
" $w=Get-CimInstance Win32_Process -Filter \"ProcessId=$cpid\" -EA SilentlyContinue",
|
|
130
|
+
" if(-not $w){break}",
|
|
131
|
+
" Write-Output \"$($w.Name)|$($w.ProcessId)|$($w.ParentProcessId)\"",
|
|
132
|
+
" $cpid=$w.ParentProcessId",
|
|
142
133
|
"}",
|
|
143
134
|
].join(";");
|
|
144
135
|
const output = execFileSync("powershell", ["-NoProfile", "-Command", script], {
|
|
@@ -149,10 +140,11 @@ function detectWindows() {
|
|
|
149
140
|
for (const line of output.split("\n")) {
|
|
150
141
|
const parts = line.trim().split("|");
|
|
151
142
|
if (parts.length < 3) continue;
|
|
152
|
-
|
|
143
|
+
// WMI 返回的 Name 带 .exe 后缀,去掉再匹配
|
|
144
|
+
const name = parts[0].replace(/\.exe$/i, "").toLowerCase();
|
|
153
145
|
const pid = parseInt(parts[1], 10);
|
|
154
146
|
if (WIN_TERMINAL_NAMES.has(name)) {
|
|
155
|
-
return { name: parts[0], pid, processName: name + ".exe" };
|
|
147
|
+
return { name: parts[0].replace(/\.exe$/i, ""), pid, processName: name + ".exe" };
|
|
156
148
|
}
|
|
157
149
|
}
|
|
158
150
|
} catch {
|
|
@@ -161,26 +153,6 @@ function detectWindows() {
|
|
|
161
153
|
return null;
|
|
162
154
|
}
|
|
163
155
|
|
|
164
|
-
/**
|
|
165
|
-
* 按名称查找进程(Windows)
|
|
166
|
-
*/
|
|
167
|
-
function findProcessByName(name) {
|
|
168
|
-
try {
|
|
169
|
-
const script = `$p=Get-Process -Name '${name}' -EA SilentlyContinue | Select-Object -First 1; if($p){Write-Output "$($p.ProcessName)|$($p.Id)"}`;
|
|
170
|
-
const output = execFileSync("powershell", ["-NoProfile", "-Command", script], {
|
|
171
|
-
encoding: "utf-8",
|
|
172
|
-
timeout: 5000,
|
|
173
|
-
}).trim();
|
|
174
|
-
if (output) {
|
|
175
|
-
const parts = output.split("|");
|
|
176
|
-
return { name: parts[0], pid: parseInt(parts[1], 10), processName: parts[0].toLowerCase() + ".exe" };
|
|
177
|
-
}
|
|
178
|
-
} catch {
|
|
179
|
-
// 查找失败
|
|
180
|
-
}
|
|
181
|
-
return null;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
156
|
function detectLinux() {
|
|
185
157
|
// 1. 环境变量快速检测
|
|
186
158
|
if (process.env.VSCODE_PID) {
|
package/src/notify.js
CHANGED
|
@@ -255,7 +255,8 @@ async function sendNotification(options = {}) {
|
|
|
255
255
|
method = "notify-helper";
|
|
256
256
|
command = path.join(__dirname, "..", "vendor", "notify-helper", "ClaudeCodeNotify.exe");
|
|
257
257
|
const targetNames = ["WindowsTerminal", "Code", "cursor", "cmd", "pwsh", "powershell", "mintty"];
|
|
258
|
-
|
|
258
|
+
const pid = (terminalInfo && terminalInfo.pid) ? String(terminalInfo.pid) : "0";
|
|
259
|
+
args = [title, message, targetNames.join(","), pid];
|
|
259
260
|
}
|
|
260
261
|
|
|
261
262
|
const result = { sent: !dryRun, method, command, args, activate: !!activateCmd };
|
|
Binary file
|
|
@@ -16,6 +16,17 @@ class NotifyHelper
|
|
|
16
16
|
[DllImport("user32.dll")]
|
|
17
17
|
static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
|
|
18
18
|
|
|
19
|
+
delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
|
|
20
|
+
|
|
21
|
+
[DllImport("user32.dll")]
|
|
22
|
+
static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);
|
|
23
|
+
|
|
24
|
+
[DllImport("user32.dll")]
|
|
25
|
+
static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
|
|
26
|
+
|
|
27
|
+
[DllImport("user32.dll")]
|
|
28
|
+
static extern bool IsWindowVisible(IntPtr hWnd);
|
|
29
|
+
|
|
19
30
|
// Claude sparkle 图标 PNG(内嵌,无需外部文件)
|
|
20
31
|
static string IconBase64 = "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";
|
|
21
32
|
|
|
@@ -27,9 +38,30 @@ class NotifyHelper
|
|
|
27
38
|
string title = args.Length > 0 ? args[0] : "Claude Code";
|
|
28
39
|
string message = args.Length > 1 ? args[1] : "任务完成";
|
|
29
40
|
string targetName = args.Length > 2 ? args[2] : "";
|
|
41
|
+
int targetPid = 0;
|
|
42
|
+
if (args.Length > 3) int.TryParse(args[3], out targetPid);
|
|
43
|
+
|
|
44
|
+
// 策略 1:通过 PID 精确定位终端窗口(多实例场景)
|
|
45
|
+
if (targetPid > 0)
|
|
46
|
+
{
|
|
47
|
+
try
|
|
48
|
+
{
|
|
49
|
+
var proc = Process.GetProcessById(targetPid);
|
|
50
|
+
if (proc.MainWindowHandle != IntPtr.Zero)
|
|
51
|
+
{
|
|
52
|
+
targetHandle = proc.MainWindowHandle;
|
|
53
|
+
}
|
|
54
|
+
else
|
|
55
|
+
{
|
|
56
|
+
// MainWindowHandle 可能为零(如 Windows Terminal),用 EnumWindows 查找
|
|
57
|
+
targetHandle = FindWindowByPid(targetPid);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch { /* 进程可能已退出,回退到名称查找 */ }
|
|
61
|
+
}
|
|
30
62
|
|
|
31
|
-
//
|
|
32
|
-
if (!string.IsNullOrEmpty(targetName))
|
|
63
|
+
// 策略 2:通过进程名查找(回退方案)
|
|
64
|
+
if (targetHandle == IntPtr.Zero && !string.IsNullOrEmpty(targetName))
|
|
33
65
|
{
|
|
34
66
|
foreach (var name in targetName.Split(','))
|
|
35
67
|
{
|
|
@@ -71,6 +103,23 @@ class NotifyHelper
|
|
|
71
103
|
Application.Run();
|
|
72
104
|
}
|
|
73
105
|
|
|
106
|
+
static IntPtr FindWindowByPid(int pid)
|
|
107
|
+
{
|
|
108
|
+
IntPtr found = IntPtr.Zero;
|
|
109
|
+
EnumWindows((hWnd, lParam) =>
|
|
110
|
+
{
|
|
111
|
+
uint procId;
|
|
112
|
+
GetWindowThreadProcessId(hWnd, out procId);
|
|
113
|
+
if ((int)procId == pid && IsWindowVisible(hWnd))
|
|
114
|
+
{
|
|
115
|
+
found = hWnd;
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
return true;
|
|
119
|
+
}, IntPtr.Zero);
|
|
120
|
+
return found;
|
|
121
|
+
}
|
|
122
|
+
|
|
74
123
|
static Icon LoadEmbeddedIcon()
|
|
75
124
|
{
|
|
76
125
|
try
|