feat(pi-notifications): use 'tell application' for notifications to suppress Show button

- Tell target app (default: Ghostty) to display notification instead of raw osascript
- This attributes notification to the app, avoiding the 'Show' button that opens Script Editor
- Configurable via PI_NOTIFICATION_APP env var
- test-notify.ts falls back to plain display notification if target app isn't running
- Synced to auto-discovery extension path
This commit is contained in:
2026-04-28 12:56:02 +01:00
parent 383cb46fe7
commit 040513e1d6
4 changed files with 60 additions and 11 deletions
+4 -1
View File
@@ -10,6 +10,7 @@ const agentEndEnabled = process.env.PI_NOTIFICATION_AGENT_END !== "false";
const debug = process.env.PI_NOTIFICATION_DEBUG === "true";
const title = process.env.PI_NOTIFICATION_TITLE || "pi";
const sound = process.env.PI_NOTIFICATION_SOUND || "default";
const targetApp = process.env.PI_NOTIFICATION_APP || "Ghostty";
function notify(body: string, subtitle?: string): void {
if (!enabled) return;
@@ -17,8 +18,10 @@ function notify(body: string, subtitle?: string): void {
const sub = subtitle ? `subtitle "${subtitle}"` : "";
// "default" is a reserved word in AppleScript, so only add sound param if it's a custom sound
const snd = sound && sound !== "default" ? `sound "${sound}"` : "";
// Tell the target app to display the notification — avoids the "Show" button
// that appears when osascript is the attributed app
execSync(
`osascript -e 'display notification "${body}" with title "${title}" ${sub} ${snd}'`.trim(),
`osascript -e 'tell application "${targetApp}" to display notification "${body}" with title "${title}" ${sub} ${snd}'`.trim(),
{ stdio: "ignore" }
);
} catch {
+33 -6
View File
@@ -9,17 +9,44 @@ import { execSync } from "node:child_process";
const title = process.env.PI_NOTIFICATION_TITLE || "pi";
const sound = process.env.PI_NOTIFICATION_SOUND || "default";
const targetApp = process.env.PI_NOTIFICATION_APP || "Ghostty";
// "default" is a reserved word in AppleScript, so only add sound param if it's a custom sound
const soundArg = sound && sound !== "default" ? `sound "${sound}"` : "";
try {
function tryNotify(tellApp: string): string {
const cmd = `osascript -e 'tell application "${tellApp}" to display notification "Test notification from pi-notifications" with title "${title}" ${soundArg}'`.trim();
return cmd;
}
function tryPlainNotify(): string {
const cmd = `osascript -e 'display notification "Test notification from pi-notifications" with title "${title}" ${soundArg}'`.trim();
console.log("[test-notify] running:", cmd);
execSync(cmd, { stdio: ["ignore", "pipe", "pipe"] });
return cmd;
}
let cmd = tryNotify(targetApp);
let success = false;
let lastError: string | undefined;
// Try telling the target app first, fall back to plain display notification
for (const attempt of [
{ cmd: tryNotify(targetApp), label: `tell "${targetApp}"` },
{ cmd: tryPlainNotify(), label: "plain" },
]) {
try {
console.log(`[test-notify] trying ${attempt.label}:`, attempt.cmd);
execSync(attempt.cmd, { stdio: ["ignore", "pipe", "pipe"] });
success = true;
break;
} catch (e: any) {
lastError = e.message;
console.log(`[test-notify] ${attempt.label} failed:`, e.message.split("\n")[0]);
}
}
if (success) {
console.log("[test-notify] ✅ Notification sent — check your Notification Center");
} catch (e: any) {
console.error("[test-notify] ❌ Failed:", e.message);
console.error("[test-notify] Try running the command directly in bash to verify osascript works.");
} else {
console.error("[test-notify] ❌ Failed:", lastError);
process.exit(1);
}