opencode-smart-voice-notify 1.3.0 → 1.3.2

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.
@@ -1,372 +1,372 @@
1
- import os from 'os';
2
- import path from 'path';
3
- import fs from 'fs';
4
- import { exec } from 'child_process';
5
- import { promisify } from 'util';
6
- import detectTerminal from 'detect-terminal';
7
-
8
- /**
9
- * Focus Detection Module for OpenCode Smart Voice Notify
10
- *
11
- * Detects whether the user is currently looking at the OpenCode terminal.
12
- * Used to suppress notifications when the user is already focused on the terminal.
13
- *
14
- * Platform support:
15
- * - macOS: Full support using AppleScript to check frontmost app
16
- * - Windows: Not supported (returns false - no reliable API)
17
- * - Linux: Not supported (returns false - varies by desktop environment)
18
- *
19
- * @module util/focus-detect
20
- * @see docs/ARCHITECT_PLAN.md - Phase 3, Task 3.2
21
- */
22
-
23
- const execAsync = promisify(exec);
24
-
25
- // ========================================
26
- // CACHING CONFIGURATION
27
- // ========================================
28
-
29
- /**
30
- * Cache for focus detection results.
31
- * Prevents excessive system calls (AppleScript execution).
32
- */
33
- let focusCache = {
34
- isFocused: false,
35
- timestamp: 0,
36
- terminalName: null
37
- };
38
-
39
- /**
40
- * Cache TTL in milliseconds.
41
- * Focus detection results are cached for this duration.
42
- * 500ms provides a good balance between responsiveness and performance.
43
- */
44
- const CACHE_TTL_MS = 500;
45
-
46
- /**
47
- * List of known terminal application names for macOS.
48
- * These are matched against the frontmost application name.
49
- * The detect-terminal package helps identify which terminal is in use.
50
- */
51
- export const KNOWN_TERMINALS_MACOS = [
52
- 'Terminal',
53
- 'iTerm',
54
- 'iTerm2',
55
- 'Hyper',
56
- 'Alacritty',
57
- 'kitty',
58
- 'WezTerm',
59
- 'Tabby',
60
- 'Warp',
61
- 'Rio',
62
- 'Ghostty',
63
- // VS Code and other IDEs with integrated terminals
64
- 'Code',
65
- 'Visual Studio Code',
66
- 'VSCodium',
67
- 'Cursor',
68
- 'Windsurf',
69
- 'Zed',
70
- // JetBrains IDEs
71
- 'IntelliJ IDEA',
72
- 'WebStorm',
73
- 'PyCharm',
74
- 'PhpStorm',
75
- 'GoLand',
76
- 'RubyMine',
77
- 'CLion',
78
- 'DataGrip',
79
- 'Rider',
80
- 'Android Studio'
81
- ];
82
-
83
- // ========================================
84
- // DEBUG LOGGING
85
- // ========================================
86
-
87
- /**
88
- * Debug logging to file.
89
- * Only logs when enabled.
90
- * Writes to ~/.config/opencode/logs/smart-voice-notify-debug.log
91
- *
92
- * @param {string} message - Message to log
93
- * @param {boolean} enabled - Whether debug logging is enabled
94
- */
95
- const debugLog = (message, enabled = false) => {
96
- if (!enabled) return;
97
-
98
- try {
99
- const configDir = process.env.OPENCODE_CONFIG_DIR || path.join(os.homedir(), '.config', 'opencode');
100
- const logsDir = path.join(configDir, 'logs');
101
-
102
- if (!fs.existsSync(logsDir)) {
103
- fs.mkdirSync(logsDir, { recursive: true });
104
- }
105
-
106
- const logFile = path.join(logsDir, 'smart-voice-notify-debug.log');
107
- const timestamp = new Date().toISOString();
108
- fs.appendFileSync(logFile, `[${timestamp}] [focus-detect] ${message}\n`);
109
- } catch (e) {
110
- // Silently fail - logging should never break the plugin
111
- }
112
- };
113
-
114
- // ========================================
115
- // PLATFORM DETECTION
116
- // ========================================
117
-
118
- /**
119
- * Get the current platform identifier.
120
- * @returns {'darwin' | 'win32' | 'linux'} Platform string
121
- */
122
- export const getPlatform = () => os.platform();
123
-
124
- /**
125
- * Check if focus detection is supported on this platform.
126
- *
127
- * @returns {{ supported: boolean, reason?: string }} Support status
128
- */
129
- export const isFocusDetectionSupported = () => {
130
- const platform = getPlatform();
131
-
132
- switch (platform) {
133
- case 'darwin':
134
- return { supported: true };
135
- case 'win32':
136
- return { supported: false, reason: 'Windows focus detection not supported - no reliable API' };
137
- case 'linux':
138
- return { supported: false, reason: 'Linux focus detection not supported - varies by desktop environment' };
139
- default:
140
- return { supported: false, reason: `Unsupported platform: ${platform}` };
141
- }
142
- };
143
-
144
- // ========================================
145
- // TERMINAL DETECTION
146
- // ========================================
147
-
148
- /**
149
- * Detect the current terminal emulator using detect-terminal package.
150
- * Caches the result since the terminal doesn't change during execution.
151
- *
152
- * @param {boolean} debug - Enable debug logging
153
- * @returns {string | null} Terminal name or null if not detected
154
- */
155
- let cachedTerminalName = null;
156
- let terminalDetectionAttempted = false;
157
-
158
- export const getTerminalName = (debug = false) => {
159
- // Return cached result if already detected
160
- if (terminalDetectionAttempted) {
161
- return cachedTerminalName;
162
- }
163
-
164
- try {
165
- terminalDetectionAttempted = true;
166
- // Prefer the outer terminal (GUI app) over multiplexers like tmux/screen
167
- const terminal = detectTerminal({ preferOuter: true });
168
- cachedTerminalName = terminal || null;
169
- debugLog(`Detected terminal: ${cachedTerminalName}`, debug);
170
- return cachedTerminalName;
171
- } catch (e) {
172
- debugLog(`Terminal detection failed: ${e.message}`, debug);
173
- return null;
174
- }
175
- };
176
-
177
- // ========================================
178
- // FOCUS DETECTION - macOS
179
- // ========================================
180
-
181
- /**
182
- * AppleScript to get the frontmost application name.
183
- * Uses System Events to determine which app is currently focused.
184
- */
185
- const APPLESCRIPT_GET_FRONTMOST = `
186
- tell application "System Events"
187
- set frontApp to first application process whose frontmost is true
188
- return name of frontApp
189
- end tell
190
- `;
191
-
192
- /**
193
- * Get the name of the frontmost application on macOS.
194
- *
195
- * @param {boolean} debug - Enable debug logging
196
- * @returns {Promise<string | null>} Frontmost app name or null on error
197
- */
198
- const getFrontmostAppMacOS = async (debug = false) => {
199
- try {
200
- const { stdout } = await execAsync(`osascript -e '${APPLESCRIPT_GET_FRONTMOST}'`, {
201
- timeout: 2000, // 2 second timeout
202
- maxBuffer: 1024 // Small buffer - we only expect app name
203
- });
204
-
205
- const appName = stdout.trim();
206
- debugLog(`Frontmost app: "${appName}"`, debug);
207
- return appName;
208
- } catch (e) {
209
- debugLog(`Failed to get frontmost app: ${e.message}`, debug);
210
- return null;
211
- }
212
- };
213
-
214
- /**
215
- * Check if the frontmost app is a known terminal on macOS.
216
- *
217
- * @param {string} appName - The frontmost application name
218
- * @param {boolean} debug - Enable debug logging
219
- * @returns {boolean} True if the app is a known terminal
220
- */
221
- const isKnownTerminal = (appName, debug = false) => {
222
- if (!appName) return false;
223
-
224
- // Direct match
225
- if (KNOWN_TERMINALS_MACOS.some(t => t.toLowerCase() === appName.toLowerCase())) {
226
- debugLog(`"${appName}" is a known terminal (direct match)`, debug);
227
- return true;
228
- }
229
-
230
- // Partial match (for apps like "iTerm2" matching "iTerm")
231
- if (KNOWN_TERMINALS_MACOS.some(t => appName.toLowerCase().includes(t.toLowerCase()))) {
232
- debugLog(`"${appName}" is a known terminal (partial match)`, debug);
233
- return true;
234
- }
235
-
236
- // Check if the detected terminal from detect-terminal matches
237
- const detectedTerminal = getTerminalName(debug);
238
- if (detectedTerminal && appName.toLowerCase().includes(detectedTerminal.toLowerCase())) {
239
- debugLog(`"${appName}" matches detected terminal "${detectedTerminal}"`, debug);
240
- return true;
241
- }
242
-
243
- debugLog(`"${appName}" is NOT a known terminal`, debug);
244
- return false;
245
- };
246
-
247
- // ========================================
248
- // MAIN FOCUS DETECTION FUNCTION
249
- // ========================================
250
-
251
- /**
252
- * Check if the OpenCode terminal is currently focused.
253
- *
254
- * This function detects whether the user is currently looking at the terminal
255
- * where OpenCode is running. Used to suppress notifications when the user
256
- * is already paying attention to the terminal.
257
- *
258
- * Platform behavior:
259
- * - macOS: Uses AppleScript to check the frontmost application
260
- * - Windows: Always returns false (not supported)
261
- * - Linux: Always returns false (not supported)
262
- *
263
- * Results are cached for 500ms to avoid excessive system calls.
264
- *
265
- * @param {object} [options={}] - Options
266
- * @param {boolean} [options.debugLog=false] - Enable debug logging
267
- * @returns {Promise<boolean>} True if terminal is focused, false otherwise
268
- *
269
- * @example
270
- * const focused = await isTerminalFocused({ debugLog: true });
271
- * if (focused) {
272
- * console.log('User is looking at the terminal - skip notification');
273
- * }
274
- */
275
- export const isTerminalFocused = async (options = {}) => {
276
- const debug = options?.debugLog || false;
277
- const now = Date.now();
278
-
279
- // Check cache first
280
- if (now - focusCache.timestamp < CACHE_TTL_MS) {
281
- debugLog(`Using cached focus result: ${focusCache.isFocused}`, debug);
282
- return focusCache.isFocused;
283
- }
284
-
285
- const platform = getPlatform();
286
-
287
- // Platform-specific implementation
288
- if (platform === 'darwin') {
289
- try {
290
- const frontmostApp = await getFrontmostAppMacOS(debug);
291
- const isFocused = isKnownTerminal(frontmostApp, debug);
292
-
293
- // Update cache
294
- focusCache = {
295
- isFocused,
296
- timestamp: now,
297
- terminalName: frontmostApp
298
- };
299
-
300
- debugLog(`Focus detection complete: ${isFocused} (frontmost: "${frontmostApp}")`, debug);
301
- return isFocused;
302
- } catch (e) {
303
- debugLog(`Focus detection error: ${e.message}`, debug);
304
- // On error, assume not focused (fail open - still notify)
305
- focusCache = {
306
- isFocused: false,
307
- timestamp: now,
308
- terminalName: null
309
- };
310
- return false;
311
- }
312
- }
313
-
314
- // Windows and Linux: Not supported
315
- if (platform === 'win32') {
316
- debugLog('Focus detection not supported on Windows', debug);
317
- } else if (platform === 'linux') {
318
- debugLog('Focus detection not supported on Linux', debug);
319
- } else {
320
- debugLog(`Focus detection not supported on platform: ${platform}`, debug);
321
- }
322
-
323
- // Cache the result even for unsupported platforms
324
- focusCache = {
325
- isFocused: false,
326
- timestamp: now,
327
- terminalName: null
328
- };
329
-
330
- return false;
331
- };
332
-
333
- /**
334
- * Clear the focus detection cache.
335
- * Useful for testing or when forcing a fresh check.
336
- */
337
- export const clearFocusCache = () => {
338
- focusCache = {
339
- isFocused: false,
340
- timestamp: 0,
341
- terminalName: null
342
- };
343
- };
344
-
345
- /**
346
- * Reset the terminal detection cache.
347
- * Useful for testing.
348
- */
349
- export const resetTerminalDetection = () => {
350
- cachedTerminalName = null;
351
- terminalDetectionAttempted = false;
352
- };
353
-
354
- /**
355
- * Get the current cache state.
356
- * Useful for testing and debugging.
357
- *
358
- * @returns {{ isFocused: boolean, timestamp: number, terminalName: string | null }} Cache state
359
- */
360
- export const getCacheState = () => ({ ...focusCache });
361
-
362
- // Default export for convenience
363
- export default {
364
- isTerminalFocused,
365
- isFocusDetectionSupported,
366
- getTerminalName,
367
- getPlatform,
368
- clearFocusCache,
369
- resetTerminalDetection,
370
- getCacheState,
371
- KNOWN_TERMINALS_MACOS
372
- };
1
+ import os from 'os';
2
+ import path from 'path';
3
+ import fs from 'fs';
4
+ import { exec } from 'child_process';
5
+ import { promisify } from 'util';
6
+ import detectTerminal from 'detect-terminal';
7
+
8
+ /**
9
+ * Focus Detection Module for OpenCode Smart Voice Notify
10
+ *
11
+ * Detects whether the user is currently looking at the OpenCode terminal.
12
+ * Used to suppress notifications when the user is already focused on the terminal.
13
+ *
14
+ * Platform support:
15
+ * - macOS: Full support using AppleScript to check frontmost app
16
+ * - Windows: Not supported (returns false - no reliable API)
17
+ * - Linux: Not supported (returns false - varies by desktop environment)
18
+ *
19
+ * @module util/focus-detect
20
+ * @see docs/ARCHITECT_PLAN.md - Phase 3, Task 3.2
21
+ */
22
+
23
+ const execAsync = promisify(exec);
24
+
25
+ // ========================================
26
+ // CACHING CONFIGURATION
27
+ // ========================================
28
+
29
+ /**
30
+ * Cache for focus detection results.
31
+ * Prevents excessive system calls (AppleScript execution).
32
+ */
33
+ let focusCache = {
34
+ isFocused: false,
35
+ timestamp: 0,
36
+ terminalName: null
37
+ };
38
+
39
+ /**
40
+ * Cache TTL in milliseconds.
41
+ * Focus detection results are cached for this duration.
42
+ * 500ms provides a good balance between responsiveness and performance.
43
+ */
44
+ const CACHE_TTL_MS = 500;
45
+
46
+ /**
47
+ * List of known terminal application names for macOS.
48
+ * These are matched against the frontmost application name.
49
+ * The detect-terminal package helps identify which terminal is in use.
50
+ */
51
+ export const KNOWN_TERMINALS_MACOS = [
52
+ 'Terminal',
53
+ 'iTerm',
54
+ 'iTerm2',
55
+ 'Hyper',
56
+ 'Alacritty',
57
+ 'kitty',
58
+ 'WezTerm',
59
+ 'Tabby',
60
+ 'Warp',
61
+ 'Rio',
62
+ 'Ghostty',
63
+ // VS Code and other IDEs with integrated terminals
64
+ 'Code',
65
+ 'Visual Studio Code',
66
+ 'VSCodium',
67
+ 'Cursor',
68
+ 'Windsurf',
69
+ 'Zed',
70
+ // JetBrains IDEs
71
+ 'IntelliJ IDEA',
72
+ 'WebStorm',
73
+ 'PyCharm',
74
+ 'PhpStorm',
75
+ 'GoLand',
76
+ 'RubyMine',
77
+ 'CLion',
78
+ 'DataGrip',
79
+ 'Rider',
80
+ 'Android Studio'
81
+ ];
82
+
83
+ // ========================================
84
+ // DEBUG LOGGING
85
+ // ========================================
86
+
87
+ /**
88
+ * Debug logging to file.
89
+ * Only logs when enabled.
90
+ * Writes to ~/.config/opencode/logs/smart-voice-notify-debug.log
91
+ *
92
+ * @param {string} message - Message to log
93
+ * @param {boolean} enabled - Whether debug logging is enabled
94
+ */
95
+ const debugLog = (message, enabled = false) => {
96
+ if (!enabled) return;
97
+
98
+ try {
99
+ const configDir = process.env.OPENCODE_CONFIG_DIR || path.join(os.homedir(), '.config', 'opencode');
100
+ const logsDir = path.join(configDir, 'logs');
101
+
102
+ if (!fs.existsSync(logsDir)) {
103
+ fs.mkdirSync(logsDir, { recursive: true });
104
+ }
105
+
106
+ const logFile = path.join(logsDir, 'smart-voice-notify-debug.log');
107
+ const timestamp = new Date().toISOString();
108
+ fs.appendFileSync(logFile, `[${timestamp}] [focus-detect] ${message}\n`);
109
+ } catch (e) {
110
+ // Silently fail - logging should never break the plugin
111
+ }
112
+ };
113
+
114
+ // ========================================
115
+ // PLATFORM DETECTION
116
+ // ========================================
117
+
118
+ /**
119
+ * Get the current platform identifier.
120
+ * @returns {'darwin' | 'win32' | 'linux'} Platform string
121
+ */
122
+ export const getPlatform = () => os.platform();
123
+
124
+ /**
125
+ * Check if focus detection is supported on this platform.
126
+ *
127
+ * @returns {{ supported: boolean, reason?: string }} Support status
128
+ */
129
+ export const isFocusDetectionSupported = () => {
130
+ const platform = getPlatform();
131
+
132
+ switch (platform) {
133
+ case 'darwin':
134
+ return { supported: true };
135
+ case 'win32':
136
+ return { supported: false, reason: 'Windows focus detection not supported - no reliable API' };
137
+ case 'linux':
138
+ return { supported: false, reason: 'Linux focus detection not supported - varies by desktop environment' };
139
+ default:
140
+ return { supported: false, reason: `Unsupported platform: ${platform}` };
141
+ }
142
+ };
143
+
144
+ // ========================================
145
+ // TERMINAL DETECTION
146
+ // ========================================
147
+
148
+ /**
149
+ * Detect the current terminal emulator using detect-terminal package.
150
+ * Caches the result since the terminal doesn't change during execution.
151
+ *
152
+ * @param {boolean} debug - Enable debug logging
153
+ * @returns {string | null} Terminal name or null if not detected
154
+ */
155
+ let cachedTerminalName = null;
156
+ let terminalDetectionAttempted = false;
157
+
158
+ export const getTerminalName = (debug = false) => {
159
+ // Return cached result if already detected
160
+ if (terminalDetectionAttempted) {
161
+ return cachedTerminalName;
162
+ }
163
+
164
+ try {
165
+ terminalDetectionAttempted = true;
166
+ // Prefer the outer terminal (GUI app) over multiplexers like tmux/screen
167
+ const terminal = detectTerminal({ preferOuter: true });
168
+ cachedTerminalName = terminal || null;
169
+ debugLog(`Detected terminal: ${cachedTerminalName}`, debug);
170
+ return cachedTerminalName;
171
+ } catch (e) {
172
+ debugLog(`Terminal detection failed: ${e.message}`, debug);
173
+ return null;
174
+ }
175
+ };
176
+
177
+ // ========================================
178
+ // FOCUS DETECTION - macOS
179
+ // ========================================
180
+
181
+ /**
182
+ * AppleScript to get the frontmost application name.
183
+ * Uses System Events to determine which app is currently focused.
184
+ */
185
+ const APPLESCRIPT_GET_FRONTMOST = `
186
+ tell application "System Events"
187
+ set frontApp to first application process whose frontmost is true
188
+ return name of frontApp
189
+ end tell
190
+ `;
191
+
192
+ /**
193
+ * Get the name of the frontmost application on macOS.
194
+ *
195
+ * @param {boolean} debug - Enable debug logging
196
+ * @returns {Promise<string | null>} Frontmost app name or null on error
197
+ */
198
+ const getFrontmostAppMacOS = async (debug = false) => {
199
+ try {
200
+ const { stdout } = await execAsync(`osascript -e '${APPLESCRIPT_GET_FRONTMOST}'`, {
201
+ timeout: 2000, // 2 second timeout
202
+ maxBuffer: 1024 // Small buffer - we only expect app name
203
+ });
204
+
205
+ const appName = stdout.trim();
206
+ debugLog(`Frontmost app: "${appName}"`, debug);
207
+ return appName;
208
+ } catch (e) {
209
+ debugLog(`Failed to get frontmost app: ${e.message}`, debug);
210
+ return null;
211
+ }
212
+ };
213
+
214
+ /**
215
+ * Check if the frontmost app is a known terminal on macOS.
216
+ *
217
+ * @param {string} appName - The frontmost application name
218
+ * @param {boolean} debug - Enable debug logging
219
+ * @returns {boolean} True if the app is a known terminal
220
+ */
221
+ const isKnownTerminal = (appName, debug = false) => {
222
+ if (!appName) return false;
223
+
224
+ // Direct match
225
+ if (KNOWN_TERMINALS_MACOS.some(t => t.toLowerCase() === appName.toLowerCase())) {
226
+ debugLog(`"${appName}" is a known terminal (direct match)`, debug);
227
+ return true;
228
+ }
229
+
230
+ // Partial match (for apps like "iTerm2" matching "iTerm")
231
+ if (KNOWN_TERMINALS_MACOS.some(t => appName.toLowerCase().includes(t.toLowerCase()))) {
232
+ debugLog(`"${appName}" is a known terminal (partial match)`, debug);
233
+ return true;
234
+ }
235
+
236
+ // Check if the detected terminal from detect-terminal matches
237
+ const detectedTerminal = getTerminalName(debug);
238
+ if (detectedTerminal && appName.toLowerCase().includes(detectedTerminal.toLowerCase())) {
239
+ debugLog(`"${appName}" matches detected terminal "${detectedTerminal}"`, debug);
240
+ return true;
241
+ }
242
+
243
+ debugLog(`"${appName}" is NOT a known terminal`, debug);
244
+ return false;
245
+ };
246
+
247
+ // ========================================
248
+ // MAIN FOCUS DETECTION FUNCTION
249
+ // ========================================
250
+
251
+ /**
252
+ * Check if the OpenCode terminal is currently focused.
253
+ *
254
+ * This function detects whether the user is currently looking at the terminal
255
+ * where OpenCode is running. Used to suppress notifications when the user
256
+ * is already paying attention to the terminal.
257
+ *
258
+ * Platform behavior:
259
+ * - macOS: Uses AppleScript to check the frontmost application
260
+ * - Windows: Always returns false (not supported)
261
+ * - Linux: Always returns false (not supported)
262
+ *
263
+ * Results are cached for 500ms to avoid excessive system calls.
264
+ *
265
+ * @param {object} [options={}] - Options
266
+ * @param {boolean} [options.debugLog=false] - Enable debug logging
267
+ * @returns {Promise<boolean>} True if terminal is focused, false otherwise
268
+ *
269
+ * @example
270
+ * const focused = await isTerminalFocused({ debugLog: true });
271
+ * if (focused) {
272
+ * console.log('User is looking at the terminal - skip notification');
273
+ * }
274
+ */
275
+ export const isTerminalFocused = async (options = {}) => {
276
+ const debug = options?.debugLog || false;
277
+ const now = Date.now();
278
+
279
+ // Check cache first
280
+ if (now - focusCache.timestamp < CACHE_TTL_MS) {
281
+ debugLog(`Using cached focus result: ${focusCache.isFocused}`, debug);
282
+ return focusCache.isFocused;
283
+ }
284
+
285
+ const platform = getPlatform();
286
+
287
+ // Platform-specific implementation
288
+ if (platform === 'darwin') {
289
+ try {
290
+ const frontmostApp = await getFrontmostAppMacOS(debug);
291
+ const isFocused = isKnownTerminal(frontmostApp, debug);
292
+
293
+ // Update cache
294
+ focusCache = {
295
+ isFocused,
296
+ timestamp: now,
297
+ terminalName: frontmostApp
298
+ };
299
+
300
+ debugLog(`Focus detection complete: ${isFocused} (frontmost: "${frontmostApp}")`, debug);
301
+ return isFocused;
302
+ } catch (e) {
303
+ debugLog(`Focus detection error: ${e.message}`, debug);
304
+ // On error, assume not focused (fail open - still notify)
305
+ focusCache = {
306
+ isFocused: false,
307
+ timestamp: now,
308
+ terminalName: null
309
+ };
310
+ return false;
311
+ }
312
+ }
313
+
314
+ // Windows and Linux: Not supported
315
+ if (platform === 'win32') {
316
+ debugLog('Focus detection not supported on Windows', debug);
317
+ } else if (platform === 'linux') {
318
+ debugLog('Focus detection not supported on Linux', debug);
319
+ } else {
320
+ debugLog(`Focus detection not supported on platform: ${platform}`, debug);
321
+ }
322
+
323
+ // Cache the result even for unsupported platforms
324
+ focusCache = {
325
+ isFocused: false,
326
+ timestamp: now,
327
+ terminalName: null
328
+ };
329
+
330
+ return false;
331
+ };
332
+
333
+ /**
334
+ * Clear the focus detection cache.
335
+ * Useful for testing or when forcing a fresh check.
336
+ */
337
+ export const clearFocusCache = () => {
338
+ focusCache = {
339
+ isFocused: false,
340
+ timestamp: 0,
341
+ terminalName: null
342
+ };
343
+ };
344
+
345
+ /**
346
+ * Reset the terminal detection cache.
347
+ * Useful for testing.
348
+ */
349
+ export const resetTerminalDetection = () => {
350
+ cachedTerminalName = null;
351
+ terminalDetectionAttempted = false;
352
+ };
353
+
354
+ /**
355
+ * Get the current cache state.
356
+ * Useful for testing and debugging.
357
+ *
358
+ * @returns {{ isFocused: boolean, timestamp: number, terminalName: string | null }} Cache state
359
+ */
360
+ export const getCacheState = () => ({ ...focusCache });
361
+
362
+ // Default export for convenience
363
+ export default {
364
+ isTerminalFocused,
365
+ isFocusDetectionSupported,
366
+ getTerminalName,
367
+ getPlatform,
368
+ clearFocusCache,
369
+ resetTerminalDetection,
370
+ getCacheState,
371
+ KNOWN_TERMINALS_MACOS
372
+ };