Skip to content

Commit 6582fc0

Browse files
authored
Merge pull request #12 from MasuRii/feature/typescript-migration
feat: complete TypeScript migration with cross-platform focus detection
2 parents efc75c3 + fe62cd5 commit 6582fc0

52 files changed

Lines changed: 8995 additions & 2962 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,7 @@ tests/.env.local
2525

2626
# Coverage reports
2727
coverage/
28+
29+
# Build output
30+
dist/
31+
*.tsbuildinfo

README.md

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ The plugin automatically tries multiple TTS engines in order, falling back if on
5757
### System Integration
5858
- **Native Desktop Notifications**: Windows (Toast), macOS (Notification Center), and Linux (notify-send) support
5959
- **Native Edge TTS**: No external dependencies (Python/pip) required
60-
- **Focus Detection** (macOS): Suppresses notifications when terminal is focused
60+
- **Focus Detection** (Cross-platform): Suppresses notifications when terminal is focused (Windows, macOS, Linux)
6161
- **Webhook Integration**: Receive notifications on Discord or any custom webhook endpoint when tasks finish or need attention
6262
- **Themed Sound Packs**: Use custom sound collections (e.g., Warcraft, StarCraft) by simply pointing to a directory
6363
- **Per-Project Sounds**: Assign unique sounds to different projects for easy identification
@@ -155,8 +155,9 @@ If you prefer to create the config manually, add a `smart-voice-notify.jsonc` fi
155155
"ttsReminderDelaySeconds": 30,
156156
"enableFollowUpReminders": true,
157157

158-
// Focus Detection (macOS only)
159-
"suppressWhenFocused": true,
158+
// Focus Detection (suppress notifications when terminal is focused)
159+
// Default: false (notifications always play)
160+
"suppressWhenFocused": false,
160161
"alwaysNotify": false,
161162

162163
// AI-generated messages (optional - requires local AI server)
@@ -325,7 +326,7 @@ You can replace individual sound files with entire "Sound Themes" (like the clas
325326
| **TTS (Windows SAPI)** ||||
326327
| **TTS (macOS Say)** ||||
327328
| **Desktop Notifications** ||| ✅ (req libnotify) |
328-
| **Focus Detection** | || |
329+
| **Focus Detection** | || |
329330
| **Webhook Integration** ||||
330331
| **Wake Monitor** ||| ✅ (X11/Gnome) |
331332
| **Volume Control** ||| ✅ (Pulse/ALSA) |
@@ -373,15 +374,15 @@ You can replace individual sound files with entire "Sound Themes" (like the clas
373374
- **Linux**: `paplay` or `aplay`
374375

375376
### For Focus Detection
376-
Focus detection suppresses sound and desktop notifications when the terminal is focused.
377+
Focus detection suppresses sound and desktop notifications when the terminal is focused. Also detects minimized or hidden terminal windows.
377378

378379
| Platform | Support | Notes |
379380
|----------|---------|-------|
380381
| **macOS** | ✅ Full | Uses AppleScript to detect frontmost application |
381-
| **Windows** | ❌ Not supported | No reliable API available |
382-
| **Linux** | ❌ Not supported | Varies by desktop environment |
382+
| **Windows** | ✅ Full | Uses native window focus and visibility detection |
383+
| **Linux** | ✅ Full | Uses `xdotool` / `xprop` (X11) or `gdbus` (Wayland) |
383384

384-
> **Note**: On unsupported platforms, notifications are always sent (fail-open behavior). TTS reminders are never suppressed, even when focused, since users may step away after seeing the toast.
385+
> **Note**: If focus detection fails on any platform, notifications are still sent (fail-open behavior). TTS reminders are never suppressed, even when focused, since users may step away after seeing the toast.
385386
386387
### For Webhook Notifications
387388
- **Discord**: Full support for Discord's webhook embed format.
@@ -555,7 +556,7 @@ When a user asks you to set up this plugin, follow these steps:
555556
### Platform-Specific Notes
556557
557558
- **Windows**: All features supported, SAPI as offline fallback
558-
- **macOS**: Focus detection available, `say` command as offline fallback
559+
- **macOS**: `say` command as offline fallback
559560
- **Linux**: Requires `libnotify-bin` for desktop notifications, no offline TTS fallback
560561
561562
### TTS Fallback Chain

bunfig.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
# Test patterns: ["**/*.test.js", "**/*.spec.js"] (Bun's default)
77

88
# Preload file for test environment setup
9-
preload = ["./tests/setup.js"]
9+
preload = ["./tests/setup.ts"]
1010

1111
# Test execution timeout in milliseconds (10 seconds)
1212
timeout = 10000

example.config.jsonc

Lines changed: 123 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
// ============================================================
1818

1919
// Internal version tracking - DO NOT REMOVE
20-
"_configVersion": "1.2.5",
20+
"_configVersion": "1.3.3",
2121

2222
// ============================================================
2323
// PLUGIN ENABLE/DISABLE
@@ -78,7 +78,7 @@
7878
// ============================================================
7979
// 'openai' - OpenAI-compatible TTS (Self-hosted/Cloud, e.g. Kokoro, LocalAI)
8080
// 'elevenlabs' - Best quality, anime-like voices (requires API key, free tier: 10k chars/month)
81-
// 'edge' - Good quality neural voices (Free, Native Node.js implementation)
81+
// 'edge' - Good quality neural voices (Python edge-tts CLI RECOMMENDED, with msedge-tts npm fallback)
8282
// 'sapi' - Windows built-in voices (free, offline, robotic)
8383
"ttsEngine": "elevenlabs",
8484

@@ -109,9 +109,10 @@
109109
"elevenLabsStyle": 0.5, // Style exaggeration (higher = more expressive)
110110

111111
// ============================================================
112-
// EDGE TTS SETTINGS (Free Neural Voices - Fallback)
112+
// EDGE TTS SETTINGS (Free Neural Voices)
113113
// ============================================================
114-
// Native Node.js implementation (No external dependencies)
114+
// Uses Python edge-tts CLI (RECOMMENDED, pip install edge-tts) with automatic
115+
// fallback to msedge-tts npm package if Python is not available.
115116

116117
// Voice options (run 'edge-tts --list-voices' to see all):
117118
// 'en-US-AnaNeural' - Young, cute, cartoon-like (RECOMMENDED)
@@ -133,6 +134,11 @@
133134

134135
// Voice (run PowerShell to list all installed voices):
135136
// Add-Type -AssemblyName System.Speech; (New-Object System.Speech.Synthesis.SpeechSynthesizer).GetInstalledVoices() | % { $_.VoiceInfo.Name }
137+
//
138+
// Common Windows voices:
139+
// 'Microsoft Zira Desktop' - Female, US English
140+
// 'Microsoft David Desktop' - Male, US English
141+
// 'Microsoft Hazel Desktop' - Female, UK English
136142
"sapiVoice": "Microsoft Zira Desktop",
137143

138144
// Speech rate: -10 (slowest) to +10 (fastest), 0 is normal
@@ -221,13 +227,22 @@
221227
],
222228

223229
// ============================================================
224-
// PERMISSION BATCHING
230+
// PERMISSION BATCHING (Multiple permissions at once)
225231
// ============================================================
232+
// When multiple permissions arrive simultaneously, batch them into one notification
233+
// This prevents overlapping sounds when 5+ permissions come at once
234+
235+
// Batch window (ms) - how long to wait for more permissions before notifying
226236
"permissionBatchWindowMs": 800,
227237

228238
// ============================================================
229-
// QUESTION TOOL MESSAGES (SDK v1.1.7+)
239+
// QUESTION TOOL SETTINGS (SDK v1.1.7+ - Agent asking user questions)
230240
// ============================================================
241+
// The "question" tool allows the LLM to ask users questions during execution.
242+
// This is useful for gathering preferences, clarifying instructions, or getting
243+
// decisions on implementation choices.
244+
245+
// Messages when agent asks user a question
231246
"questionTTSMessages": [
232247
"Hey! I have a question for you. Please check your screen.",
233248
"Attention! I need your input to continue.",
@@ -256,12 +271,19 @@
256271
"Still waiting for your answers on {count} questions! The task is on hold.",
257272
"Your input is needed! {count} questions are pending your response."
258273
],
274+
// Delay (in seconds) before question reminder fires
259275
"questionReminderDelaySeconds": 25,
276+
277+
// Question batch window (ms) - how long to wait for more questions before notifying
260278
"questionBatchWindowMs": 800,
261279

262280
// ============================================================
263-
// ERROR NOTIFICATION SETTINGS
281+
// ERROR NOTIFICATION SETTINGS (Session Errors)
264282
// ============================================================
283+
// Notify users when the agent encounters an error during execution.
284+
// Error notifications use more urgent messaging to get user attention.
285+
286+
// Messages when agent encounters an error
265287
"errorTTSMessages": [
266288
"Oops! Something went wrong. Please check for errors.",
267289
"Alert! The agent encountered an error and needs your attention.",
@@ -290,17 +312,51 @@
290312
"Still waiting! {count} errors need your attention.",
291313
"Don't forget! There are {count} unresolved errors in your session."
292314
],
315+
// Delay (in seconds) before error reminder fires (shorter than idle for urgency)
293316
"errorReminderDelaySeconds": 20,
294317

295318
// ============================================================
296-
// AI MESSAGE GENERATION
319+
// AI MESSAGE GENERATION (OpenAI-Compatible Endpoints)
297320
// ============================================================
321+
// Use a local/self-hosted AI to generate dynamic notification messages
322+
// instead of using preset static messages. The AI generates the text,
323+
// which is then spoken by your configured TTS engine (ElevenLabs, Edge, etc.)
324+
//
325+
// Supports: Ollama, LM Studio, LocalAI, vLLM, llama.cpp, Jan.ai, and any
326+
// OpenAI-compatible endpoint. You provide your own endpoint URL and API key.
327+
298328
"enableAIMessages": false,
329+
330+
// Your AI server endpoint URL (e.g., Ollama: http://localhost:11434/v1)
331+
// Common endpoints:
332+
// Ollama: http://localhost:11434/v1
333+
// LM Studio: http://localhost:1234/v1
334+
// LocalAI: http://localhost:8080/v1
335+
// vLLM: http://localhost:8000/v1
336+
// Jan.ai: http://localhost:1337/v1
299337
"aiEndpoint": "http://localhost:11434/v1",
338+
339+
// Model name to use (depends on what's loaded in your AI server)
340+
// Examples: "llama3", "mistral", "phi3", "gemma2", "qwen2"
300341
"aiModel": "llama3",
342+
343+
// API key for your AI server (leave empty for Ollama/LM Studio/LocalAI)
344+
// Only needed if your server requires authentication
301345
"aiApiKey": "",
346+
347+
// Request timeout in milliseconds (local AI can be slow on first request)
302348
"aiTimeout": 15000,
349+
350+
// Fallback to static preset messages if AI generation fails
303351
"aiFallbackToStatic": true,
352+
353+
// Enable context-aware AI messages (includes project name, task title, and change summary)
354+
// When enabled, AI-generated notifications will include relevant context like:
355+
// - Project name (e.g., "Your work on MyProject is complete!")
356+
// - Task/session title if available
357+
// - Change summary (files modified, lines added/deleted)
358+
// Disabled by default - enable this for more personalized notifications
359+
"enableContextAwareAI": false,
304360
"aiPrompts": {
305361
"idle": "Generate a single brief, friendly notification sentence (max 15 words) saying a coding task is complete. Be encouraging and warm. Output only the message, no quotes.",
306362
"permission": "Generate a single brief, urgent but friendly notification sentence (max 15 words) asking the user to approve a permission request. Output only the message, no quotes.",
@@ -332,35 +388,91 @@
332388
// ============================================================
333389
// DESKTOP NOTIFICATION SETTINGS
334390
// ============================================================
391+
// Native desktop notifications (Windows Toast, macOS Notification Center, Linux notify-send)
392+
// These appear as system notifications alongside sound and TTS.
393+
//
394+
// Note: On Linux, you may need to install libnotify-bin:
395+
// Ubuntu/Debian: sudo apt install libnotify-bin
396+
// Fedora: sudo dnf install libnotify
397+
// Arch: sudo pacman -S libnotify
398+
399+
// Enable native desktop notifications
335400
"enableDesktopNotification": true,
401+
402+
// How long the notification stays on screen (in seconds)
403+
// Note: Some platforms may ignore this (especially Windows 10+)
336404
"desktopNotificationTimeout": 5,
405+
406+
// Include the project name in notification titles for easier identification
407+
// Example: "OpenCode - MyProject" instead of just "OpenCode"
337408
"showProjectInNotification": true,
338409

339410
// ============================================================
340411
// FOCUS DETECTION SETTINGS
341412
// ============================================================
342-
"suppressWhenFocused": true,
413+
// Suppress sound/desktop notifications when terminal window is focused.
414+
// Cross-platform: Windows, macOS, and Linux (X11 via xdotool/xprop, Wayland via gdbus).
415+
// Default: false (notifications always play regardless of focus)
416+
// Set to true to avoid notification spam when actively working in terminal
417+
"suppressWhenFocused": false,
343418
"alwaysNotify": false,
344419

345420
// ============================================================
346-
// WEBHOOK NOTIFICATION SETTINGS
421+
// WEBHOOK NOTIFICATION SETTINGS (Discord/Generic)
347422
// ============================================================
423+
// Send notifications to a Discord webhook or any compatible endpoint.
424+
// This allows you to receive notifications on your phone or other devices.
425+
426+
// Enable webhook notifications
348427
"enableWebhook": false,
428+
429+
// Webhook URL (e.g., https://discord.com/api/webhooks/...)
349430
"webhookUrl": "",
431+
432+
// Username to show in the webhook message
350433
"webhookUsername": "OpenCode Notify",
434+
435+
// Events that should trigger a webhook notification
436+
// Options: "idle", "permission", "error", "question"
351437
"webhookEvents": ["idle", "permission", "error", "question"],
438+
439+
// Mention @everyone on permission requests (Discord only)
352440
"webhookMentionOnPermission": false,
353441

354442
// ============================================================
355-
// SOUND THEME SETTINGS
443+
// SOUND THEME SETTINGS (Themed Sound Packs)
356444
// ============================================================
445+
// Configure a directory containing custom sound files for notifications.
446+
// This allows you to use themed sound packs (e.g., Warcraft, StarCraft, etc.)
447+
//
448+
// Directory structure should contain:
449+
// /path/to/theme/idle/ - Sounds for task completion
450+
// /path/to/theme/permission/ - Sounds for permission requests
451+
// /path/to/theme/error/ - Sounds for agent errors
452+
// /path/to/theme/question/ - Sounds for agent questions
453+
//
454+
// If a specific event folder is missing, it falls back to default sounds.
455+
456+
// Path to your custom sound theme directory (absolute path recommended)
357457
"soundThemeDir": "",
458+
459+
// Pick a random sound from the appropriate theme folder for each notification
358460
"randomizeSoundFromTheme": true,
359461

360462
// ============================================================
361463
// PER-PROJECT SOUND SETTINGS
362464
// ============================================================
465+
// Assign a unique notification sound to each project based on its path.
466+
// This helps you distinguish which project is notifying you when working
467+
// on multiple tasks simultaneously.
468+
//
469+
// Note: Requires sounds named 'ding1.mp3' through 'ding6.mp3' in your
470+
// assets/ folder. If disabled, default sound files are used.
471+
472+
// Enable unique sounds per project
363473
"perProjectSounds": false,
474+
475+
// Seed value to change sound assignments (0-999)
364476
"projectSoundSeed": 0,
365477

366478
// General options

package.json

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
{
22
"name": "opencode-smart-voice-notify",
3-
"version": "1.3.2",
3+
"version": "1.3.3",
44
"description": "Smart voice notification plugin for OpenCode with multiple TTS engines (ElevenLabs, Edge TTS, Windows SAPI), AI-generated dynamic messages, and intelligent reminder system",
5-
"main": "index.js",
5+
"main": "dist/index.js",
6+
"types": "dist/index.d.ts",
67
"type": "module",
78
"scripts": {
9+
"build": "tsc -p tsconfig.build.json",
10+
"build:types": "tsc -p tsconfig.build.json --emitDeclarationOnly",
11+
"typecheck": "tsc --noEmit",
812
"test": "bun test",
913
"test:watch": "bun test --watch",
1014
"test:coverage": "bun test --coverage"
@@ -30,10 +34,8 @@
3034
"local-ai"
3135
],
3236
"files": [
33-
"index.js",
34-
"util/",
35-
"assets/",
36-
"example.config.jsonc"
37+
"dist/",
38+
"assets/"
3739
],
3840
"repository": {
3941
"type": "git",
@@ -48,12 +50,18 @@
4850
"bun": ">=1.0.0"
4951
},
5052
"dependencies": {
51-
"@elevenlabs/elevenlabs-js": "^2.32.0",
53+
"@elevenlabs/elevenlabs-js": "^2.36.0",
5254
"detect-terminal": "^2.0.0",
5355
"msedge-tts": "^2.0.4",
5456
"node-notifier": "^10.0.1"
5557
},
5658
"peerDependencies": {
5759
"@opencode-ai/plugin": "^1.1.8"
60+
},
61+
"devDependencies": {
62+
"@types/node": "^20.19.33",
63+
"@types/node-notifier": "^8.0.5",
64+
"bun-types": "^1.3.9",
65+
"typescript": "^5.9.3"
5866
}
5967
}

0 commit comments

Comments
 (0)