klaudio 0.11.2 → 0.11.3
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/README.md +96 -96
- package/bin/cli.js +44 -44
- package/package.json +40 -44
- package/src/cache.js +306 -306
- package/src/cli.js +1821 -1821
- package/src/extractor.js +213 -213
- package/src/installer.js +369 -368
- package/src/notify.js +138 -135
- package/src/player.js +488 -488
- package/src/presets.js +87 -87
- package/src/scanner.js +445 -445
- package/src/scumm.js +560 -560
- package/src/tts.js +391 -391
package/src/notify.js
CHANGED
|
@@ -1,135 +1,138 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
|
-
import { platform } from "node:os";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Send a native OS notification (fire-and-forget).
|
|
6
|
-
* Click-to-focus: activates the terminal or editor that triggered it.
|
|
7
|
-
*
|
|
8
|
-
* Windows: WinRT toast (Win10+), focuses Windows Terminal or VS Code on click
|
|
9
|
-
* macOS: terminal-notifier (if installed) or osascript fallback
|
|
10
|
-
* Linux: notify-send
|
|
11
|
-
*/
|
|
12
|
-
export function sendNotification(title, body) {
|
|
13
|
-
const os = platform();
|
|
14
|
-
try {
|
|
15
|
-
if (os === "win32") return notifyWindows(title, body);
|
|
16
|
-
if (os === "darwin") return notifyMac(title, body);
|
|
17
|
-
return notifyLinux(title, body);
|
|
18
|
-
} catch {
|
|
19
|
-
return Promise.resolve();
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Detect the terminal/editor environment.
|
|
25
|
-
*/
|
|
26
|
-
function detectTerminal() {
|
|
27
|
-
const tp = process.env.TERM_PROGRAM;
|
|
28
|
-
if (tp === "vscode") return "vscode";
|
|
29
|
-
if (tp === "cursor") return "cursor";
|
|
30
|
-
if (tp === "iTerm.app") return "iterm";
|
|
31
|
-
if (tp === "Apple_Terminal") return "terminal";
|
|
32
|
-
if (process.env.WT_SESSION) return "windows-terminal";
|
|
33
|
-
// Fallback: check PATH for clues (hooks inherit the terminal's env)
|
|
34
|
-
const path = process.env.PATH || "";
|
|
35
|
-
if (/cursor[/\\]/i.test(path) && /resources[/\\]app[/\\]bin/i.test(path)) return "cursor";
|
|
36
|
-
if (/VS Code[/\\]bin/i.test(path) || /Code[/\\]bin/i.test(path)) return "vscode";
|
|
37
|
-
return "unknown";
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function escapeXml(s) {
|
|
41
|
-
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// ── Windows ──────────────────────────────────────────────────────
|
|
45
|
-
|
|
46
|
-
function notifyWindows(title, body) {
|
|
47
|
-
const safeTitle = escapeXml(title);
|
|
48
|
-
const safeBody = escapeXml(body);
|
|
49
|
-
|
|
50
|
-
// Determine activation strategy based on the terminal we're running in
|
|
51
|
-
let toastAttrs = "";
|
|
52
|
-
let appId;
|
|
53
|
-
const terminal = detectTerminal();
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
[
|
|
72
|
-
|
|
73
|
-
$
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { platform } from "node:os";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Send a native OS notification (fire-and-forget).
|
|
6
|
+
* Click-to-focus: activates the terminal or editor that triggered it.
|
|
7
|
+
*
|
|
8
|
+
* Windows: WinRT toast (Win10+), focuses Windows Terminal or VS Code on click
|
|
9
|
+
* macOS: terminal-notifier (if installed) or osascript fallback
|
|
10
|
+
* Linux: notify-send
|
|
11
|
+
*/
|
|
12
|
+
export function sendNotification(title, body) {
|
|
13
|
+
const os = platform();
|
|
14
|
+
try {
|
|
15
|
+
if (os === "win32") return notifyWindows(title, body);
|
|
16
|
+
if (os === "darwin") return notifyMac(title, body);
|
|
17
|
+
return notifyLinux(title, body);
|
|
18
|
+
} catch {
|
|
19
|
+
return Promise.resolve();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Detect the terminal/editor environment.
|
|
25
|
+
*/
|
|
26
|
+
function detectTerminal() {
|
|
27
|
+
const tp = process.env.TERM_PROGRAM;
|
|
28
|
+
if (tp === "vscode") return "vscode";
|
|
29
|
+
if (tp === "cursor") return "cursor";
|
|
30
|
+
if (tp === "iTerm.app") return "iterm";
|
|
31
|
+
if (tp === "Apple_Terminal") return "terminal";
|
|
32
|
+
if (process.env.WT_SESSION) return "windows-terminal";
|
|
33
|
+
// Fallback: check PATH for clues (hooks inherit the terminal's env)
|
|
34
|
+
const path = process.env.PATH || "";
|
|
35
|
+
if (/cursor[/\\]/i.test(path) && /resources[/\\]app[/\\]bin/i.test(path)) return "cursor";
|
|
36
|
+
if (/VS Code[/\\]bin/i.test(path) || /Code[/\\]bin/i.test(path)) return "vscode";
|
|
37
|
+
return "unknown";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function escapeXml(s) {
|
|
41
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ── Windows ──────────────────────────────────────────────────────
|
|
45
|
+
|
|
46
|
+
function notifyWindows(title, body) {
|
|
47
|
+
const safeTitle = escapeXml(title);
|
|
48
|
+
const safeBody = escapeXml(body);
|
|
49
|
+
|
|
50
|
+
// Determine activation strategy based on the terminal we're running in
|
|
51
|
+
let toastAttrs = "";
|
|
52
|
+
let appId;
|
|
53
|
+
const terminal = detectTerminal();
|
|
54
|
+
|
|
55
|
+
// Windows requires a registered AUMID for toasts to actually show.
|
|
56
|
+
// Use Windows Terminal's AUMID as default (works on most Win10+ systems).
|
|
57
|
+
// For VS Code/Cursor, also add protocol activation so clicking focuses the editor.
|
|
58
|
+
appId = "Microsoft.WindowsTerminal_8wekyb3d8bbwe!App";
|
|
59
|
+
|
|
60
|
+
if (terminal === "vscode" || terminal === "cursor") {
|
|
61
|
+
const protocol = terminal === "cursor" ? "cursor://" : "vscode://";
|
|
62
|
+
toastAttrs = ` activationType="protocol" launch="${protocol}"`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const toastXml = `<toast${toastAttrs}><visual><binding template="ToastGeneric"><text>${safeTitle}</text><text>${safeBody}</text></binding></visual></toast>`;
|
|
66
|
+
|
|
67
|
+
// PowerShell script: show WinRT toast notification
|
|
68
|
+
const ps = `\
|
|
69
|
+
[void][Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime]
|
|
70
|
+
[void][Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom, ContentType = WindowsRuntime]
|
|
71
|
+
$x = [Windows.Data.Xml.Dom.XmlDocument]::new()
|
|
72
|
+
$x.LoadXml('${toastXml.replace(/'/g, "''")}')
|
|
73
|
+
$t = [Windows.UI.Notifications.ToastNotification]::new($x)
|
|
74
|
+
[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier('${appId}').Show($t)`;
|
|
75
|
+
|
|
76
|
+
// Run detached so the Node process can exit immediately
|
|
77
|
+
const child = spawn("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", ps], {
|
|
78
|
+
windowsHide: true,
|
|
79
|
+
detached: true,
|
|
80
|
+
stdio: "ignore",
|
|
81
|
+
});
|
|
82
|
+
child.unref();
|
|
83
|
+
return Promise.resolve();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ── macOS ────────────────────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
function notifyMac(title, body) {
|
|
89
|
+
try {
|
|
90
|
+
// Determine which app to activate when the notification is clicked
|
|
91
|
+
const terminal = detectTerminal();
|
|
92
|
+
const bundleIds = {
|
|
93
|
+
vscode: "com.microsoft.VSCode",
|
|
94
|
+
cursor: "com.todesktop.230313mzl4w4u92",
|
|
95
|
+
iterm: "com.googlecode.iterm2",
|
|
96
|
+
terminal: "com.apple.Terminal",
|
|
97
|
+
};
|
|
98
|
+
const bundleId = bundleIds[terminal] || "com.apple.Terminal";
|
|
99
|
+
|
|
100
|
+
// Try terminal-notifier first (best UX: click-to-focus), fall back to osascript
|
|
101
|
+
return new Promise((resolve) => {
|
|
102
|
+
const child = spawn("terminal-notifier", [
|
|
103
|
+
"-title", title, "-message", body,
|
|
104
|
+
"-activate", bundleId, "-sender", bundleId,
|
|
105
|
+
], { stdio: "ignore" });
|
|
106
|
+
|
|
107
|
+
child.on("error", () => {
|
|
108
|
+
// terminal-notifier not installed — fall back to osascript
|
|
109
|
+
try {
|
|
110
|
+
const safeTitle = title.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
111
|
+
const safeBody = body.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
112
|
+
const script = `display notification "${safeBody}" with title "${safeTitle}"`;
|
|
113
|
+
const child2 = spawn("osascript", ["-e", script], {
|
|
114
|
+
stdio: "ignore",
|
|
115
|
+
detached: true,
|
|
116
|
+
});
|
|
117
|
+
child2.unref();
|
|
118
|
+
} catch { /* ignore */ }
|
|
119
|
+
resolve();
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
child.on("close", () => resolve());
|
|
123
|
+
});
|
|
124
|
+
} catch {
|
|
125
|
+
return Promise.resolve();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ── Linux ────────────────────────────────────────────────────────
|
|
130
|
+
|
|
131
|
+
function notifyLinux(title, body) {
|
|
132
|
+
const child = spawn("notify-send", ["-a", "klaudio", title, body], {
|
|
133
|
+
stdio: "ignore",
|
|
134
|
+
detached: true,
|
|
135
|
+
});
|
|
136
|
+
child.unref();
|
|
137
|
+
return Promise.resolve();
|
|
138
|
+
}
|