claude-hook-notify 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/notify.js +44 -15
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-hook-notify",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "🔔 Claude Code 任务完成桌面通知 — 一键安装,跨平台支持",
5
5
  "main": "src/index.js",
6
6
  "bin": {
package/src/notify.js CHANGED
@@ -234,20 +234,38 @@ async function sendNotification(options = {}) {
234
234
  return result;
235
235
  }
236
236
  } else if (platform === "win32") {
237
- method = "powershell";
237
+ method = "powershell-toast";
238
238
  command = "powershell.exe";
239
- const psScript = `
240
- [void][System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms');
241
- $n = New-Object System.Windows.Forms.NotifyIcon;
242
- $n.Icon = [System.Drawing.SystemIcons]::Information;
243
- $n.BalloonTipTitle = '${title.replace(/'/g, "''")}';
244
- $n.BalloonTipText = '${message.replace(/'/g, "''")}';
245
- $n.Visible = $true;
246
- $n.ShowBalloonTip(5000);
247
- Start-Sleep -Seconds 6;
248
- $n.Dispose();
249
- `.replace(/\n/g, " ");
250
- args = ["-NoProfile", "-Command", psScript];
239
+ const t = title.replace(/'/g, "''");
240
+ const m = message.replace(/'/g, "''");
241
+ const appId =
242
+ "{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}\\WindowsPowerShell\\v1.0\\powershell.exe";
243
+ const psScript = [
244
+ "$t='" + t + "'",
245
+ "$m='" + m + "'",
246
+ "try{",
247
+ " [void][Windows.UI.Notifications.ToastNotificationManager,Windows.UI.Notifications,ContentType=WindowsRuntime]",
248
+ " [void][Windows.Data.Xml.Dom.XmlDocument,Windows.Data.Xml.Dom,ContentType=WindowsRuntime]",
249
+ " $x=New-Object Windows.Data.Xml.Dom.XmlDocument",
250
+ " $te=[System.Security.SecurityElement]::Escape($t)",
251
+ " $me=[System.Security.SecurityElement]::Escape($m)",
252
+ ' $x.LoadXml("<toast><visual><binding template=\'ToastGeneric\'><text>$te</text><text>$me</text></binding></visual></toast>")',
253
+ " $toast=[Windows.UI.Notifications.ToastNotification]::new($x)",
254
+ " [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier('" + appId + "').Show($toast)",
255
+ "}catch{",
256
+ " [void][System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms')",
257
+ " $n=New-Object System.Windows.Forms.NotifyIcon",
258
+ " $n.Icon=[System.Drawing.SystemIcons]::Information",
259
+ " $n.BalloonTipTitle=$t",
260
+ " $n.BalloonTipText=$m",
261
+ " $n.Visible=$true",
262
+ " $n.ShowBalloonTip(5000)",
263
+ " Start-Sleep -Seconds 6",
264
+ " $n.Dispose()",
265
+ "}",
266
+ ].join("\n");
267
+ const encoded = Buffer.from(psScript, "utf16le").toString("base64");
268
+ args = ["-NoProfile", "-EncodedCommand", encoded];
251
269
  }
252
270
 
253
271
  const result = { sent: !dryRun, method, command, args };
@@ -260,8 +278,19 @@ async function sendNotification(options = {}) {
260
278
 
261
279
  try {
262
280
  if (command) {
263
- const { execFileSync } = require("child_process");
264
- execFileSync(command, args, { stdio: "ignore", timeout: 5000 });
281
+ if (platform === "win32") {
282
+ // Windows: spawn detached so hook exits immediately, notification lives independently
283
+ const { spawn } = require("child_process");
284
+ const child = spawn(command, args, {
285
+ detached: true,
286
+ stdio: "ignore",
287
+ windowsHide: true,
288
+ });
289
+ child.unref();
290
+ } else {
291
+ const { execFileSync } = require("child_process");
292
+ execFileSync(command, args, { stdio: "ignore", timeout: 5000 });
293
+ }
265
294
  }
266
295
  } catch (err) {
267
296
  result.sent = false;