From 499ed3244e1a9d382b836b41b24c6c8759bf63e5 Mon Sep 17 00:00:00 2001 From: Hannes Rudolph Date: Tue, 26 Aug 2025 15:32:13 -0600 Subject: [PATCH] refactor: replace osascript with node-notifier for macOS notifications - Replaced direct osascript calls with node-notifier library - Added proper TypeScript types with @types/node-notifier - Improved error handling with fallback to osascript - Added icon support for notifications - Better cross-platform compatibility --- pnpm-lock.yaml | 66 +++++++++++++++++++------ src/integrations/notifications/index.ts | 43 ++++++++++++++-- src/package.json | 2 + 3 files changed, 91 insertions(+), 20 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 68e0120391..136030119b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -676,6 +676,9 @@ importers: node-ipc: specifier: ^12.0.0 version: 12.0.0 + node-notifier: + specifier: ^10.0.1 + version: 10.0.1 ollama: specifier: ^0.5.17 version: 0.5.17 @@ -812,6 +815,9 @@ importers: '@types/node-ipc': specifier: ^9.2.3 version: 9.2.3 + '@types/node-notifier': + specifier: ^8.0.5 + version: 8.0.5 '@types/proper-lockfile': specifier: ^4.1.4 version: 4.1.4 @@ -4057,6 +4063,9 @@ packages: '@types/node-ipc@9.2.3': resolution: {integrity: sha512-/MvSiF71fYf3+zwqkh/zkVkZj1hl1Uobre9EMFy08mqfJNAmpR0vmPgOUdEIDVgifxHj6G1vYMPLSBLLxoDACQ==} + '@types/node-notifier@8.0.5': + resolution: {integrity: sha512-LX7+8MtTsv6szumAp6WOy87nqMEdGhhry/Qfprjm1Ma6REjVzeF7SCyvPtp5RaF6IkXCS9V4ra8g5fwvf2ZAYg==} + '@types/node@12.20.55': resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} @@ -4072,9 +4081,6 @@ packages: '@types/node@20.17.57': resolution: {integrity: sha512-f3T4y6VU4fVQDKVqJV4Uppy8c1p/sVvS3peyqxyWnzkqXFJLRU7Y1Bl7rMS1Qe9z0v4M6McY0Fp9yBsgHJUsWQ==} - '@types/node@20.19.11': - resolution: {integrity: sha512-uug3FEEGv0r+jrecvUUpbY8lLisvIjg6AAic6a2bSP5OEOLeJsDSnvhCDov7ipFFMXS3orMpzlmi0ZcuGkBbow==} - '@types/node@24.2.1': resolution: {integrity: sha512-DRh5K+ka5eJic8CjH7td8QpYEV6Zo10gfRkjHCO3weqZHWDtAaSTFtl4+VMqOJ4N5jcuhZ9/l+yy8rVgw7BQeQ==} @@ -6265,6 +6271,9 @@ packages: resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==} engines: {node: '>=6.0'} + growly@1.3.0: + resolution: {integrity: sha512-+xGQY0YyAWCnqy7Cd++hc2JqMYzlm0dG30Jd0beaA64sROr8C4nt8Yc9V5Ro3avlSUDTN0ulqP/VBKi1/lLygw==} + gtoken@7.1.0: resolution: {integrity: sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==} engines: {node: '>=14.0.0'} @@ -6578,6 +6587,11 @@ packages: is-decimal@2.0.1: resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} + is-docker@2.2.1: + resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} + engines: {node: '>=8'} + hasBin: true + is-docker@3.0.0: resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -6735,6 +6749,10 @@ packages: resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} engines: {node: '>=0.10.0'} + is-wsl@2.2.0: + resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} + engines: {node: '>=8'} + is-wsl@3.1.0: resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==} engines: {node: '>=16'} @@ -7738,6 +7756,9 @@ packages: resolution: {integrity: sha512-QHJ2gAJiqA3cM7cQiRjLsfCOBRB0TwQ6axYD4FSllQWipEbP6i7Se1dP8EzPKk5J1nCe27W69eqPmCoKyQ61Vg==} engines: {node: '>=14'} + node-notifier@10.0.1: + resolution: {integrity: sha512-YX7TSyDukOZ0g+gmzjB6abKu+hTGvO8+8+gIFDsRCU2t8fLV/P2unmt+LGFaIa4y64aX98Qksa97rgz4vMNeLQ==} + node-releases@2.0.19: resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} @@ -8795,6 +8816,9 @@ packages: resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==} engines: {node: '>= 0.4'} + shellwords@0.1.1: + resolution: {integrity: sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==} + shiki@3.4.1: resolution: {integrity: sha512-PSnoczt+iWIOB4iRQ+XVPFtTuN1FcmuYzPgUBZTSv5pC6CozssIx2M4O5n4S9gJlUu9A3FxMU0ZPaHflky/6LA==} @@ -9490,9 +9514,6 @@ packages: undici-types@6.19.8: resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} - undici-types@6.21.0: - resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} - undici-types@7.10.0: resolution: {integrity: sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==} @@ -13558,6 +13579,10 @@ snapshots: dependencies: '@types/node': 24.2.1 + '@types/node-notifier@8.0.5': + dependencies: + '@types/node': 24.2.1 + '@types/node@12.20.55': {} '@types/node@14.18.63': {} @@ -13574,11 +13599,6 @@ snapshots: dependencies: undici-types: 6.19.8 - '@types/node@20.19.11': - dependencies: - undici-types: 6.21.0 - optional: true - '@types/node@24.2.1': dependencies: undici-types: 7.10.0 @@ -13642,7 +13662,7 @@ snapshots: '@types/ws@8.18.1': dependencies: - '@types/node': 20.19.11 + '@types/node': 24.2.1 optional: true '@types/yargs-parser@21.0.3': {} @@ -16128,6 +16148,8 @@ snapshots: section-matter: 1.0.0 strip-bom-string: 1.0.0 + growly@1.3.0: {} + gtoken@7.1.0: dependencies: gaxios: 6.7.1 @@ -16502,6 +16524,8 @@ snapshots: is-decimal@2.0.1: {} + is-docker@2.2.1: {} + is-docker@3.0.0: {} is-extendable@0.1.1: {} @@ -16626,6 +16650,10 @@ snapshots: is-windows@1.0.2: {} + is-wsl@2.2.0: + dependencies: + is-docker: 2.2.1 + is-wsl@3.1.0: dependencies: is-inside-container: 1.0.0 @@ -17902,6 +17930,15 @@ snapshots: js-queue: 2.0.2 strong-type: 1.1.0 + node-notifier@10.0.1: + dependencies: + growly: 1.3.0 + is-wsl: 2.2.0 + semver: 7.7.2 + shellwords: 0.1.1 + uuid: 8.3.2 + which: 2.0.2 + node-releases@2.0.19: {} noms@0.0.0: @@ -19174,6 +19211,8 @@ snapshots: shell-quote@1.8.3: optional: true + shellwords@0.1.1: {} + shiki@3.4.1: dependencies: '@shikijs/core': 3.4.1 @@ -19934,9 +19973,6 @@ snapshots: undici-types@6.19.8: {} - undici-types@6.21.0: - optional: true - undici-types@7.10.0: {} undici@6.21.3: {} diff --git a/src/integrations/notifications/index.ts b/src/integrations/notifications/index.ts index 6962637c9e..41192b6a4a 100644 --- a/src/integrations/notifications/index.ts +++ b/src/integrations/notifications/index.ts @@ -1,6 +1,7 @@ import { execa } from "execa" import { platform } from "os" import * as vscode from "vscode" +import * as path from "path" interface NotificationOptions { title?: string @@ -10,14 +11,46 @@ interface NotificationOptions { } async function showMacOSNotification(options: NotificationOptions): Promise { - const { title, subtitle = "", message } = options - - const script = `display notification "${message}" with title "${title}" subtitle "${subtitle}" sound name "Tink"` + const { title = "Roo Code", subtitle = "", message } = options try { - await execa("osascript", ["-e", script]) + // Try to use node-notifier first (more reliable cross-platform solution) + const notifier = await import("node-notifier") + + // Get the extension's icon path + const iconPath = path.join(__dirname, "..", "..", "assets", "icons", "icon.png") + + await new Promise((resolve, reject) => { + notifier.notify( + { + title: title, + subtitle: subtitle, + message: message, + sound: "Tink", + icon: iconPath, + wait: false, + }, + (error: Error | null) => { + if (error) { + reject(error) + } else { + resolve() + } + }, + ) + }) } catch (error) { - throw new Error(`Failed to show macOS notification: ${error}`) + // Fallback to osascript if node-notifier fails + console.warn("node-notifier failed, falling back to osascript:", error) + + const escape = (str: string = "") => str.replace(/\\/g, "\\\\").replace(/"/g, '\\"') + const script = `display notification "${escape(message)}" with title "${escape(title)}" subtitle "${escape(subtitle)}" sound name "Tink"` + + try { + await execa("osascript", ["-e", script]) + } catch (osascriptError) { + throw new Error(`Failed to show macOS notification: ${osascriptError}`) + } } } diff --git a/src/package.json b/src/package.json index 52949a006a..4df3ce84f7 100644 --- a/src/package.json +++ b/src/package.json @@ -463,6 +463,7 @@ "monaco-vscode-textmate-theme-converter": "^0.1.7", "node-cache": "^5.1.2", "node-ipc": "^12.0.0", + "node-notifier": "^10.0.1", "ollama": "^0.5.17", "openai": "^5.0.0", "os-name": "^6.0.0", @@ -510,6 +511,7 @@ "@types/node": "20.x", "@types/node-cache": "^4.1.3", "@types/node-ipc": "^9.2.3", + "@types/node-notifier": "^8.0.5", "@types/proper-lockfile": "^4.1.4", "@types/ps-tree": "^1.1.6", "@types/stream-json": "^1.7.8",