soloterm / notify
A PHP package to send desktop notifications via OSC escape sequences in terminal applications.
Installs: 40
Dependents: 1
Suggesters: 0
Security: 0
Stars: 2
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/soloterm/notify
Requires
- php: ^8.1
Requires (Dev)
- phpunit/phpunit: ^10.5|^11
README
A PHP library for sending desktop notifications via OSC escape sequences in terminal applications.
This library was built to support Solo, your all-in-one Laravel command to tame local development.
| OSC Notifications (Ghostty) | OSC Notifications (iTerm2) |
![]() |
![]() |
| macOS Fallback | Fireworks (iTerm2) |
![]() |
![]() |
| Progress Bars (Ghostty) | Progress Bars (iTerm2) |
![]() |
![]() |
Why Use This Library?
Building CLI applications often requires notifying users when long-running tasks complete:
- Build finished
- Tests passed (or failed)
- Queue job completed
- Deployment done
Instead of relying on external tools like notify-send or osascript, this library uses OSC (Operating System Command) escape sequences that modern terminal emulators interpret directly. No dependencies, no shell commands, just pure PHP.
When OSC isn't supported, the library automatically falls back to system notification tools (notify-send on Linux, osascript on macOS, PowerShell on Windows).
Installation
composer require soloterm/notify
Usage
use SoloTerm\Notify\Notify; // Simple notification (message only) Notify::send('Build complete!'); // Notification with title and body Notify::send('All 142 tests passed in 3.2s', 'Tests Passed'); // Check if notifications are supported if (Notify::canNotify()) { Notify::send('This will work!'); } // Get detected terminal $terminal = Notify::getTerminal(); // 'iterm2', 'kitty', 'wezterm', etc. // Get selected protocol $protocol = Notify::getProtocol(); // 'osc9', 'osc777', 'osc99'
Real-World Examples
// After a long build process $startTime = microtime(true); // ... build logic ... $duration = round(microtime(true) - $startTime, 2); Notify::send("Completed in {$duration}s", 'Build Finished'); // After running tests $result = $testsPassed ? 'All tests passed!' : 'Some tests failed'; $title = $testsPassed ? 'Tests Passed' : 'Tests Failed'; Notify::send($result, $title); // In a queue worker while ($job = $queue->pop()) { $job->process(); Notify::send("Processed: {$job->name}", 'Queue'); }
Terminal Compatibility
| Terminal | Support | Protocol | Notes |
|---|---|---|---|
| iTerm2 | ✅ | OSC 9 | macOS |
| Kitty | ✅ | OSC 99 | Cross-platform, full-featured |
| WezTerm | ✅ | OSC 777 | Cross-platform |
| Ghostty | ✅ | OSC 777 | Cross-platform |
| Foot | ✅ | OSC 777 | Wayland |
| tmux | ✅ | Passthrough | Requires allow-passthrough on |
| GNU Screen | ✅ | Passthrough | Automatic wrapping |
| Alacritty | ⚠️ | Fallback | Uses system notifications |
| Terminal.app | ⚠️ | Fallback | Uses system notifications |
| VS Code | ⚠️ | Fallback | Uses system notifications |
| Windows Terminal | ⚠️ | Fallback | Uses system notifications |
tmux Configuration
For notifications to work inside tmux, add this to your ~/.tmux.conf:
set -g allow-passthrough on
Then reload your tmux configuration:
tmux source-file ~/.tmux.conf
OSC Protocols
This library supports three notification protocols:
OSC 9 (iTerm2)
Simple message-only notifications. Widely supported.
ESC ] 9 ; message BEL
OSC 777 (rxvt-unicode)
Supports separate title and body. Used by WezTerm, Ghostty, and VTE-based terminals.
ESC ] 777 ; notify ; title ; body BEL
OSC 99 (Kitty)
The most feature-rich protocol with support for urgency levels, notification IDs, and more.
ESC ] 99 ; metadata ; payload ST
Advanced Usage
Urgency Levels (Kitty/OSC 99 only)
use SoloTerm\Notify\Notify; // Send with explicit urgency Notify::send('Background task done', 'Info', Notify::URGENCY_LOW); Notify::send('Build complete', 'Success', Notify::URGENCY_NORMAL); Notify::send('Server down!', 'Alert', Notify::URGENCY_CRITICAL); // Convenience methods Notify::sendLow('Low priority message'); Notify::sendCritical('Critical alert!'); // Set default urgency for all notifications Notify::setDefaultUrgency(Notify::URGENCY_LOW);
Notification IDs (Kitty/OSC 99 only)
Update or dismiss existing notifications using IDs - perfect for progress indicators:
// Send notification with an ID Notify::send('Building... 0%', 'Build', id: 'build-progress'); // Update the same notification Notify::send('Building... 50%', 'Build', id: 'build-progress'); Notify::send('Building... 100%', 'Build', id: 'build-progress'); // Close/dismiss a notification by ID Notify::close('build-progress'); // Check if the terminal supports IDs $caps = Notify::capabilities(); if ($caps['supports_id']) { // Use notification IDs }
External Fallback
When OSC notifications aren't supported, the library can fall back to system tools:
- Linux:
notify-send(libnotify) - macOS:
osascript(AppleScript) - Windows: PowerShell toast notifications
// Fallback is enabled by default - disable it if needed Notify::disableFallback(); // Re-enable fallback Notify::enableFallback(); // Check if fallback is available if (Notify::canFallback()) { echo "System notifications available as fallback\n"; } // Send using any available method (OSC or fallback) Notify::sendAny('This works everywhere!', 'Hello'); // Send directly via external tools (bypassing OSC) Notify::sendExternal('Message', 'Title');
Bell Fallback
// Send bell character (works everywhere) Notify::bell(); // Try notification first, then external fallback, then bell Notify::sendOrBell('Task complete', 'Done');
Check Capabilities
$caps = Notify::capabilities(); // Returns: // [ // 'terminal' => 'kitty', // 'protocol' => 'osc99', // 'supports_title' => true, // 'supports_urgency' => true, // 'supports_id' => true, // 'supports_progress' => true, // 'in_multiplexer' => false, // 'fallback_available' => true, // ] // List all known terminals and their support $terminals = Notify::supportedTerminals(); // ['kitty' => 'osc99', 'iterm2' => 'osc9', 'alacritty' => null, ...]
Force a Specific Protocol
// Force OSC 777 regardless of detected terminal Notify::forceProtocol('osc777'); Notify::send('Using OSC 777', 'Forced'); // Reset to auto-detection Notify::forceProtocol(null);
Check Multiplexer Status
if (Notify::inTmux()) { echo "Running inside tmux\n"; } if (Notify::inScreen()) { echo "Running inside GNU Screen\n"; }
Reset State
// Useful for testing or when terminal changes Notify::reset();
Progress Bars (OSC 9;4)
Display progress in your terminal's tab or taskbar:
// Check if supported if (Notify::supportsProgress()) { // Show progress (0-100) Notify::progress(50); // Different states Notify::progressError(75); // Red - error state Notify::progressPaused(60); // Yellow - paused Notify::progressIndeterminate(); // Pulsing - unknown duration // Clear when done Notify::progressClear(); }
Supported terminals: Windows Terminal, Ghostty 1.2+, iTerm2 3.6.6+, ConEmu, Mintty
Hyperlinks (OSC 8)
Create clickable links in terminal output:
echo Notify::hyperlink('https://example.com', 'Click here'); echo Notify::hyperlink('https://example.com'); // URL as display text
Request Attention (iTerm2 only)
Notify::requestAttention(); // Bounce dock icon Notify::fireworks(); // Fireworks animation Notify::stealFocus(); // Bring window to front
How It Works
-
Terminal Detection: The library checks environment variables like
KITTY_WINDOW_ID,ITERM_SESSION_ID,WEZTERM_PANE, andTERM_PROGRAMto identify the terminal. -
Protocol Selection: Based on the detected terminal, the optimal OSC protocol is selected.
-
Sequence Building: The notification message is sanitized and formatted according to the protocol.
-
Multiplexer Passthrough: If running inside tmux or GNU Screen, the sequence is wrapped in a DCS (Device Control String) passthrough.
-
Output: The escape sequence is written directly to STDOUT, which the terminal interprets and displays as a desktop notification.
-
Fallback: If OSC isn't supported, the library automatically tries system notification tools.
Requirements
- PHP 8.1 or higher
- A supported terminal emulator (or system notification tools for fallback)
- For tmux:
allow-passthrough onin your config
Testing
composer test
Contributing
Contributions are welcome! Please feel free to submit a pull request.
License
The MIT License (MIT).
Support
This is free! If you want to support me:
- Check out my courses:
- Help spread the word about things I make
Credits
Solo was developed by Aaron Francis. If you like it, please let me know!
- Twitter: https://twitter.com/aarondfrancis
- Website: https://aaronfrancis.com
- YouTube: https://youtube.com/@aarondfrancis
- GitHub: https://github.com/aarondfrancis/solo
Related Projects
- Solo - All-in-one Laravel command for local development
- Screen - Pure PHP terminal renderer
- Dumps - Laravel command to intercept dumps
- Grapheme - Unicode grapheme width calculator
- Notify Laravel - Laravel integration for soloterm/notify
- TNotify - Standalone, cross-platform CLI for desktop notifications
- VTail - Vendor-aware tail for Laravel logs





