From 6fd7569c0c36175de20a9a677c6a5da97a41c594 Mon Sep 17 00:00:00 2001 From: ran Date: Sat, 9 Aug 2025 17:02:42 -0500 Subject: [PATCH 1/2] Add Telegram notification support for cross-platform compatibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add configuration system with config.json for notification methods - Support both terminal and Telegram notifications simultaneously - Telegram notifications work on all platforms including SSH environments - Update README with comprehensive Telegram bot setup instructions - Maintain backward compatibility with existing terminal notifications - No fallback design - crashes on errors for easier debugging šŸ¤– Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- README.md | 83 ++++++++++++++++++++++++++++++++++++++----- ccnotify.py | 100 ++++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 157 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index cfa77a7..b0d50f5 100644 --- a/README.md +++ b/README.md @@ -2,15 +2,19 @@ # CCNotify -CCNotify provides desktop notifications for Claude Code, alerting you when Claude needs your input or completes tasks. +CCNotify provides notifications for Claude Code, alerting you when Claude needs your input or completes tasks. ## Features -- šŸ”” **Get notified** when Claude requires your input or completes a task. -- šŸ”— **Click to jump back** when notifications are clicked, automatically taking you to the corresponding project in VS Code. +- šŸ”” **Get notified** when Claude requires your input or completes a task +- šŸ”— **Click to jump back** when notifications are clicked, automatically taking you to the corresponding project in VS Code (macOS only) - ā±ļø **Task Duration**: Displays started time, and how long the task took to complete +- šŸ¤– **Telegram Support**: Cross-platform notifications via Telegram bot (works on any platform, including SSH environments) +- šŸ–„ļø **Dual Mode**: Use terminal notifications, Telegram, or both simultaneously -**Note**: Currently compatible with macOS only. +**Platform Support**: +- **macOS**: Terminal notifications + Telegram +- **All platforms**: Telegram notifications (Linux, Windows, SSH environments) ## Installation Guide @@ -31,14 +35,19 @@ chmod a+x ~/.claude/ccnotify/ccnotify.py ok ``` -### 2. Install terminal-notifier -ccnotify uses `terminal-notifier` for macOS notifications. Install it using Homebrew: +### 2. Choose Your Notification Method + +#### Option A: Terminal Notifications (macOS only) +Install `terminal-notifier` for native macOS notifications: ```bash brew install terminal-notifier ``` -For alternative installation methods and more information, visit: https://github.com/julienXX/terminal-notifier +For alternative installation methods, visit: https://github.com/julienXX/terminal-notifier + +#### Option B: Telegram Notifications (All platforms) +Set up a Telegram bot for cross-platform notifications. See [Telegram Bot Setup](#telegram-bot-setup) section below. ### 3. Configure Claude Hooks Add the following hooks to your Claude configuration to enable ccnotify: @@ -88,7 +97,65 @@ To verify the notification system works, start a new Claude Code session and run ``` after 1 second, echo 'hello' ``` -You should see a macOS notification appear. +You should see a notification appear (terminal notification on macOS, or Telegram message if configured). + +## Configuration + +CCNotify creates a `config.json` file in `~/.claude/ccnotify/` on first run. You can customize notification methods: + +```json +{ + "notifications": { + "terminal": { + "enabled": true + }, + "telegram": { + "enabled": false, + "bot_token": "your_bot_token_here", + "chat_id": "your_chat_id_here" + } + } +} +``` + +**Configuration Options:** +- Set `terminal.enabled: false` to disable macOS notifications +- Set `telegram.enabled: true` and add your bot credentials to enable Telegram +- You can enable both methods simultaneously + +## Telegram Bot Setup + +### Step 1: Create a Telegram Bot +1. Open Telegram and search for `@BotFather` +2. Send `/newbot` command +3. Choose a name for your bot (e.g., "My Claude Notifier") +4. Choose a username (must end with `bot`, e.g., "my_claude_notifier_bot") +5. Copy the bot token (looks like `123456789:ABCdefGHIjklMNOpqrsTUVwxyz`) + +### Step 2: Get Your Chat ID +1. Send any message to your bot (e.g., `/start`) +2. Open this URL in your browser (replace `YOUR_BOT_TOKEN`): + ``` + https://api.telegram.org/botYOUR_BOT_TOKEN/getUpdates + ``` +3. Look for `"chat":{"id":123456789}` and copy the ID number + +### Step 3: Configure CCNotify +Edit `~/.claude/ccnotify/config.json`: +```json +{ + "notifications": { + "terminal": { + "enabled": true + }, + "telegram": { + "enabled": true, + "bot_token": "123456789:ABCdefGHIjklMNOpqrsTUVwxyz", + "chat_id": "123456789" + } + } +} +``` ## How It Works diff --git a/ccnotify.py b/ccnotify.py index 6f01be8..aaa9702 100755 --- a/ccnotify.py +++ b/ccnotify.py @@ -17,8 +17,10 @@ class ClaudePromptTracker: def __init__(self): """Initialize the prompt tracker with database setup""" script_dir = os.path.dirname(os.path.abspath(__file__)) + self.script_dir = script_dir self.db_path = os.path.join(script_dir, "ccnotify.db") self.setup_logging() + self.load_config() self.init_database() def setup_logging(self): @@ -48,6 +50,32 @@ def setup_logging(self): logger.setLevel(logging.INFO) logger.addHandler(handler) + def load_config(self): + """Load configuration from config.json""" + config_path = os.path.join(self.script_dir, "config.json") + default_config = { + "notifications": { + "terminal": { + "enabled": True + }, + "telegram": { + "enabled": False, + "bot_token": "", + "chat_id": "" + } + } + } + + if os.path.exists(config_path): + with open(config_path, 'r') as f: + self.config = json.load(f) + else: + self.config = default_config + with open(config_path, 'w') as f: + json.dump(default_config, f, indent=2) + + logging.info(f"Config loaded from {config_path}") + def init_database(self): """Create tables and triggers if they don't exist""" with sqlite3.connect(self.db_path) as conn: @@ -217,27 +245,63 @@ def calculate_duration(self, start_time, end_time): return "Unknown" def send_notification(self, title, subtitle, cwd=None): - """Send macOS notification using terminal-notifier""" + """Send notification via configured methods""" from datetime import datetime current_time = datetime.now().strftime("%B %d, %Y at %H:%M") - try: - cmd = [ - 'terminal-notifier', - '-sound', 'default', - '-title', title, - '-subtitle', f"{subtitle}\n{current_time}" - ] - - if cwd: - cmd.extend(['-execute', f'/usr/local/bin/code "{cwd}"']) - - subprocess.run(cmd, check=False, capture_output=True) - logging.info(f"Notification sent: {title} - {subtitle}") - except FileNotFoundError: - logging.warning("terminal-notifier not found, notification skipped") - except Exception as e: - logging.error(f"Error sending notification: {e}") + # Send terminal notification if enabled + if self.config["notifications"]["terminal"]["enabled"]: + self.send_terminal_notification(title, subtitle, current_time, cwd) + + # Send telegram notification if enabled + if self.config["notifications"]["telegram"]["enabled"]: + self.send_telegram_notification(title, subtitle, current_time, cwd) + + def send_terminal_notification(self, title, subtitle, current_time, cwd=None): + """Send macOS notification using terminal-notifier""" + cmd = [ + 'terminal-notifier', + '-sound', 'default', + '-title', title, + '-subtitle', f"{subtitle}\n{current_time}" + ] + + if cwd: + cmd.extend(['-execute', f'/usr/local/bin/code "{cwd}"']) + + subprocess.run(cmd, check=True, capture_output=True) + logging.info(f"Terminal notification sent: {title} - {subtitle}") + + def send_telegram_notification(self, title, subtitle, current_time, cwd=None): + """Send notification via Telegram bot""" + import urllib.request + import urllib.parse + + bot_token = self.config["notifications"]["telegram"]["bot_token"] + chat_id = self.config["notifications"]["telegram"]["chat_id"] + + if not bot_token or not chat_id: + raise ValueError("Telegram bot_token and chat_id must be configured") + + # Format message + message = f"šŸ”” *{title}*\n{subtitle}\nšŸ“… {current_time}" + if cwd: + message += f"\nšŸ“ `{cwd}`" + + # Send via Telegram Bot API + url = f"https://api.telegram.org/bot{bot_token}/sendMessage" + data = urllib.parse.urlencode({ + 'chat_id': chat_id, + 'text': message, + 'parse_mode': 'Markdown' + }).encode('utf-8') + + req = urllib.request.Request(url, data=data, method='POST') + with urllib.request.urlopen(req) as response: + if response.status != 200: + raise Exception(f"Telegram API error: {response.status}") + + logging.info(f"Telegram notification sent: {title} - {subtitle}") def validate_input_data(data, expected_event_name): From 3f01aff2ac74c99214f955613ebf17787d4e938b Mon Sep 17 00:00:00 2001 From: ran Date: Sun, 10 Aug 2025 01:56:26 -0500 Subject: [PATCH 2/2] add telecc --- README.md | 58 +++++++++++++++++++++ telegram_sender.py | 124 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 182 insertions(+) create mode 100755 telegram_sender.py diff --git a/README.md b/README.md index b0d50f5..6c8f383 100644 --- a/README.md +++ b/README.md @@ -167,6 +167,64 @@ ccnotify tracks Claude sessions and provides notifications at key moments: All activity is logged to `~/.claude/ccnotify/ccnotify.log` and session data is stored in `~/.claude/ccnotify/ccnotify.db` locally. No data is uploaded or shared externally. +## Telegram Sender Tool + +CCNotify includes a standalone `telegram_sender.py` script for sending messages directly to Telegram from command pipelines. This tool reuses the CCNotify Telegram configuration. + +### Features +- šŸ“¤ **Pipeline friendly**: Reads from stdin for easy integration +- šŸ”§ **Uses existing config**: Leverages CCNotify's Telegram bot setup +- šŸ“ **Optional titles**: Add custom titles to messages +- šŸš€ **Lightweight**: Simple script with no additional dependencies + +### Usage + +```bash +# Basic usage +echo "Task completed!" | ./telegram_sender.py + +# With custom title +echo "Training accuracy: 95.2%" | ./telegram_sender.py "ML Model Results" + +# In command pipelines +model_train | ./telegram_sender.py "Training Complete" + +# With Claude Code integration +your_command | claude "summarize this output" | ./telegram_sender.py "Command Summary" +``` + +### Creating a Convenient Alias + +For frequent use with Claude Code, create a `telecc` alias that combines Claude summarization with Telegram sending: + +```bash +# Add to your ~/.bashrc or ~/.zshrc +alias telecc='claude "Please provide a concise summary of this output, highlighting key results, errors, or important information:" | ./telegram_sender.py' + +# Usage examples: +model_training_script | telecc "Training Results" +long_running_process | telecc "Process Complete" +pytest --verbose | telecc "Test Results" +``` + +### Installation + +1. Ensure CCNotify is configured with Telegram enabled +2. Copy or link `telegram_sender.py` to your desired location: + ```bash + # Copy to local directory + cp telegram_sender.py ~/bin/ + + # Or create symlink in your PATH + ln -s $(pwd)/telegram_sender.py ~/bin/telegram_sender + ``` +3. Make sure the script is executable: `chmod +x telegram_sender.py` + +### Requirements + +- CCNotify must be installed and configured with Telegram enabled +- Valid `config.json` file at `~/.claude/ccnotify/config.json` +- Network access to Telegram Bot API ## Uninstall diff --git a/telegram_sender.py b/telegram_sender.py new file mode 100755 index 0000000..69d5377 --- /dev/null +++ b/telegram_sender.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python3 +""" +Simple Telegram Message Sender +Reuses CCNotify configuration for sending messages via Telegram Bot API. +Designed for use in command pipelines. + +Usage: + echo "message" | telegram_sender.py + echo "message" | telegram_sender.py "Custom Title" + command | telegram_sender.py "Command Output" +""" + +import os +import sys +import json +import urllib.request +import urllib.parse +from datetime import datetime + + +class TelegramSender: + def __init__(self): + """Initialize with CCNotify config""" + self.load_config() + + def load_config(self): + """Load configuration from CCNotify's config.json""" + # Use same config path as ccnotify + config_dir = os.path.expanduser("~/.claude/ccnotify") + config_path = os.path.join(config_dir, "config.json") + + if not os.path.exists(config_path): + raise FileNotFoundError( + f"CCNotify config not found at {config_path}. " + "Please run ccnotify.py first to create the configuration." + ) + + try: + with open(config_path, 'r') as f: + self.config = json.load(f) + except json.JSONDecodeError as e: + raise ValueError(f"Invalid JSON in config file: {e}") + + # Validate telegram configuration + telegram_config = self.config.get("notifications", {}).get("telegram", {}) + if not telegram_config.get("enabled", False): + raise ValueError("Telegram notifications are not enabled in CCNotify config") + + self.bot_token = telegram_config.get("bot_token", "") + self.chat_id = telegram_config.get("chat_id", "") + + if not self.bot_token or not self.chat_id: + raise ValueError("Telegram bot_token and chat_id must be configured in CCNotify config") + + def send_message(self, title, message): + """Send message via Telegram Bot API""" + # Format message - simpler than ccnotify notifications + if title: + formatted_message = f"*{title}*\n\n{message}" + else: + formatted_message = message + + # Send via Telegram Bot API + url = f"https://api.telegram.org/bot{self.bot_token}/sendMessage" + data = urllib.parse.urlencode({ + 'chat_id': self.chat_id, + 'text': formatted_message, + 'parse_mode': 'Markdown' + }).encode('utf-8') + + try: + req = urllib.request.Request(url, data=data, method='POST') + with urllib.request.urlopen(req, timeout=10) as response: + if response.status != 200: + response_text = response.read().decode('utf-8') + raise Exception(f"Telegram API error {response.status}: {response_text}") + + return True + except urllib.error.URLError as e: + raise Exception(f"Network error: {e}") + except Exception as e: + raise Exception(f"Failed to send telegram message: {e}") + + +def main(): + """Main entry point""" + try: + # Get optional title from command line argument + title = sys.argv[1] if len(sys.argv) > 1 else None + + # Read message content from stdin + if sys.stdin.isatty(): + print("Error: No input provided. Use in a pipeline or redirect input.", file=sys.stderr) + print("Usage: echo 'message' | telegram_sender.py [title]", file=sys.stderr) + sys.exit(1) + + message = sys.stdin.read().strip() + if not message: + print("Error: Empty message received from stdin", file=sys.stderr) + sys.exit(1) + + # Initialize sender and send message + sender = TelegramSender() + sender.send_message(title, message) + + # Success output for pipeline debugging + if title: + print(f"Message sent to Telegram: {title}") + else: + print("Message sent to Telegram") + + except FileNotFoundError as e: + print(f"Config Error: {e}", file=sys.stderr) + sys.exit(1) + except ValueError as e: + print(f"Configuration Error: {e}", file=sys.stderr) + sys.exit(1) + except Exception as e: + print(f"Error: {e}", file=sys.stderr) + sys.exit(1) + + +if __name__ == "__main__": + main() \ No newline at end of file