diff --git a/README.md b/README.md index 3074625..75b83dc 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ A personal AI assistant you can message from anywhere via Telegram. ## What is this? -You know how Claude Code runs in your terminal and can read files, run commands, search the web, etc? This lets you talk to it from your phone via Telegram instead of being tied to your computer. +Claude Code runs in your terminal with full tool access -- it can read files, run commands, search the web, spawn tasks, and more. This bot lets you talk to it from your phone via Telegram instead of being tied to your computer. **Text your bot → it runs Claude Code → sends back the response.** @@ -13,18 +13,33 @@ Some things you can do: - "Search the web for the latest news on X" - "Read my notes and summarize them" - "Send me a daily briefing every morning at 7am" +- "Run the tests and tell me if anything broke" +- "What did I work on yesterday?" (with Continuity Bridge) -It also works the other way - Claude can message YOU via Telegram (notifications when tasks finish, scheduled briefings, alerts, etc). +It also works the other way -- Claude can message **you** via Telegram. Notifications when tasks finish, scheduled briefings, alerts, whatever you wire up. -**The key is skills.** Out of the box, Claude Code can read files and search the web. But to make it truly YOUR assistant, create skills that give it access to your stuff: -- Google Calendar (read your schedule) -- Gmail (read-only access to emails) -- Notes app (Obsidian, Apple Notes, etc.) +**The key is skills.** Out of the box Claude Code can read files and search the web. But to make it truly your assistant, create skills that give it access to your stuff: +- Google Calendar +- Gmail +- Notes (Obsidian, etc.) - Weather for your location -- Whatever else you want +- Your Continuity Bridge context The `skills/` folder has examples to get you started. +--- + +## Platform Support + +| Platform | Bot | Voice | Service manager | +|----------|-----|-------|----------------| +| macOS (Apple Silicon) | ✅ | ✅ mlx-whisper | launchd | +| Linux | ✅ | ✅ faster-whisper | systemd | +| macOS (Intel) | ✅ | ❌ | launchd | +| Windows | Untested | Untested | — | + +--- + ## Quick Start Open Claude Code and paste: @@ -40,124 +55,160 @@ Claude will walk you through: 2. Getting your user ID from @userinfobot 3. Configuring the `.env` file 4. Installing the telegram-sender skill globally -5. Setting up launchd to run the bot continuously +5. Setting up your service manager (launchd on macOS, systemd on Linux) 6. (Optional) Setting up scheduled skills like daily briefings --- -## What's Included - -| File | Purpose | -|------|---------| -| `telegram-bot.py` | Receives messages from Telegram → sends to Claude Code | -| `skills/telegram-sender/` | Lets Claude send messages TO you | -| `skills/daily-brief/` | Example scheduled skill using telegram-sender | -| `launchd/` | Templates for running bot + scheduled skills | - -## How It Works - -**Inbound (you → Claude):** -``` -Telegram → telegram-bot.py → claude -p "message" → response → Telegram -``` - -**Outbound (Claude → you):** -``` -Claude skill → telegram-sender/send.sh → Telegram API → you -``` - ## Manual Setup -If you prefer to set up manually instead of having Claude guide you: - ### 1. Prerequisites + - [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code) installed and authenticated - Python 3.10+ ### 2. Create Telegram Bot + 1. Message [@BotFather](https://t.me/botfather) on Telegram 2. Send `/newbot` and follow prompts 3. Copy the bot token ### 3. Get Your User ID + 1. Message [@userinfobot](https://t.me/userinfobot) on Telegram 2. Copy your user ID ### 4. Configure + ```bash cp .env.example .env # Edit .env with your token and user ID ``` ### 5. Install Dependencies + ```bash pip install -r requirements.txt ``` +**Optional: Voice transcription** + +macOS (Apple Silicon): +```bash +pip install mlx-whisper +``` + +Linux: +```bash +pip install faster-whisper +``` + ### 6. Install Skill Globally + ```bash cp -r skills/telegram-sender ~/.claude/skills/ ``` -### 7. Run Bot (Manual) +### 7. Run the Bot + +**Manual (any platform):** ```bash python telegram-bot.py ``` -### 8. Run Bot (launchd - Recommended) +**macOS — launchd (runs continuously, starts on login):** ```bash # Edit launchd/com.claude.telegram-bot.plist with your paths cp launchd/com.claude.telegram-bot.plist ~/Library/LaunchAgents/ launchctl load ~/Library/LaunchAgents/com.claude.telegram-bot.plist ``` -## Bot Commands - -- `/new` - Clear session, start fresh -- `/status` - Show session status +**Linux — systemd (runs continuously, starts on login):** +```bash +# Edit paths in the service file +sed -i 's/YOUR_USERNAME/'"$USER"'/g' systemd/claude-telegram-bot.service -## Voice Messages (Apple Silicon) +# Install and start +mkdir -p ~/.config/systemd/user +cp systemd/claude-telegram-bot.service ~/.config/systemd/user/ +systemctl --user daemon-reload +systemctl --user enable --now claude-telegram-bot.service -```bash -pip install mlx-whisper +# Check it's running +systemctl --user status claude-telegram-bot.service ``` -Voice messages will be transcribed locally and sent to Claude. +See `systemd/README.md` for full Linux service instructions. + +--- + +## Bot Commands + +- `/new` — Clear session, start fresh +- `/status` — Show session status and voice support -## Example: Daily Briefing +--- -The `skills/daily-brief/` shows how to create a scheduled skill that sends you a morning briefing via Telegram. See `skills/daily-brief/SKILL.md` for details. +## Scheduling (Daily Briefings, etc.) -To schedule it: +**macOS — launchd:** ```bash -# Edit launchd/com.claude.daily-brief.plist with your paths +# Edit launchd/com.claude.daily-brief.plist with your paths and schedule cp launchd/com.claude.daily-brief.plist ~/Library/LaunchAgents/ launchctl load ~/Library/LaunchAgents/com.claude.daily-brief.plist ``` +**Linux — systemd timer:** +```bash +sed -i 's/YOUR_USERNAME/'"$USER"'/g' systemd/claude-daily-brief.service +cp systemd/claude-daily-brief.service ~/.config/systemd/user/ +cp systemd/claude-daily-brief.timer ~/.config/systemd/user/ +systemctl --user daemon-reload +systemctl --user enable --now claude-daily-brief.timer + +# Verify timer is scheduled +systemctl --user list-timers +``` + +Edit `systemd/claude-daily-brief.timer` to change the schedule. Default is 7:00 AM daily. + +--- + +## What's Included + +| Path | Purpose | +|------|---------| +| `telegram-bot.py` | Main bot -- receives messages, runs Claude Code, sends response | +| `skills/telegram-sender/` | Lets Claude send messages TO you | +| `skills/daily-brief/` | Example scheduled skill using telegram-sender | +| `launchd/` | macOS service templates | +| `systemd/` | Linux service templates | + +--- + ## Security ### ALLOWED_USERS is critical -**Always set `ALLOWED_USERS` in your `.env` file.** If left empty, anyone who discovers your bot's username can send it messages and run Claude with full tool access on your machine. +**Always set `ALLOWED_USERS` in your `.env` file.** If left empty, anyone who discovers your bot's username can send it messages and run Claude Code with full tool access on your machine. ```bash -# .env - always set this +# .env — always set this ALLOWED_USERS=123456789 ``` -Get your user ID from [@userinfobot](https://t.me/userinfobot) on Telegram. +Get your user ID from [@userinfobot](https://t.me/userinfobot). ### Understand the tool access The bot runs Claude with these tools enabled: -- `Read` / `Write` / `Edit` - file system access -- `Bash` - shell command execution -- `Glob` / `Grep` - file search -- `WebFetch` / `WebSearch` - internet access -- `Task` / `Skill` - agent spawning and skill execution +- `Read` / `Write` / `Edit` — filesystem access +- `Bash` — shell command execution +- `Glob` / `Grep` — file search +- `WebFetch` / `WebSearch` — internet access +- `Task` / `Skill` — agent spawning and skill execution -This is powerful and intentional for a personal assistant, but understand that messages you send can trigger real actions on your system. +This is powerful and intentional for a personal assistant. Messages you send can trigger real actions on your system. ### Protect your tokens @@ -167,12 +218,28 @@ This is powerful and intentional for a personal assistant, but understand that m ### Session file -Sessions are stored in `~/.telegram-claude-sessions.json`. Default file permissions apply. On shared systems, consider restricting access: +Sessions stored in `~/.telegram-claude-sessions.json`. On shared systems: ```bash chmod 600 ~/.telegram-claude-sessions.json ``` +--- + +## Viewing Logs + +**macOS:** +```bash +tail -f /tmp/telegram-bot.log +``` + +**Linux (systemd):** +```bash +journalctl --user -u claude-telegram-bot.service -f +``` + +--- + ## License MIT diff --git a/requirements.txt b/requirements.txt index cd622d4..db3a108 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,8 @@ python-telegram-bot>=20.0 mistune>=3.0 -# Optional: Voice transcription (Apple Silicon only) + +# Optional: Voice transcription (install one based on your platform) +# macOS (Apple Silicon): # mlx-whisper +# Linux: +# faster-whisper diff --git a/systemd/README.md b/systemd/README.md new file mode 100644 index 0000000..023f5e4 --- /dev/null +++ b/systemd/README.md @@ -0,0 +1,105 @@ +# systemd Service Files (Linux) + +Linux equivalent of the `launchd/` macOS service files. + +## Files + +| File | Purpose | +|------|---------| +| `claude-telegram-bot.service` | Runs the bot continuously, restarts on failure | +| `claude-daily-brief.service` | One-shot service that runs the daily briefing | +| `claude-daily-brief.timer` | Schedules `claude-daily-brief.service` daily at 7 AM | + +--- + +## Setup + +### 1. Edit paths in service files + +Open each `.service` file and replace `YOUR_USERNAME` with your actual username, +and update paths to match your installation. + +```bash +# Quick find/replace +sed -i 's/YOUR_USERNAME/'"$USER"'/g' *.service *.timer +``` + +Verify the paths look correct before proceeding. + +### 2. Install service files + +```bash +# Create systemd user directory if it doesn't exist +mkdir -p ~/.config/systemd/user + +# Copy service files +cp claude-telegram-bot.service ~/.config/systemd/user/ +cp claude-daily-brief.service ~/.config/systemd/user/ +cp claude-daily-brief.timer ~/.config/systemd/user/ + +# Reload systemd +systemctl --user daemon-reload +``` + +### 3. Start and enable the bot + +```bash +# Start now and enable on login +systemctl --user enable --now claude-telegram-bot.service + +# Check it's running +systemctl --user status claude-telegram-bot.service +``` + +### 4. Set up daily briefing (optional) + +```bash +# Enable the timer (not the service directly) +systemctl --user enable --now claude-daily-brief.timer + +# Check timer status +systemctl --user list-timers +``` + +--- + +## Managing the bot + +```bash +# View logs (live) +journalctl --user -u claude-telegram-bot.service -f + +# Stop +systemctl --user stop claude-telegram-bot.service + +# Restart +systemctl --user restart claude-telegram-bot.service + +# Disable autostart +systemctl --user disable claude-telegram-bot.service +``` + +## Autostart on boot (without login) + +By default, systemd user services only run when you're logged in. +To run the bot even when you're not logged in: + +```bash +sudo loginctl enable-linger $USER +``` + +This is useful on a home server or always-on machine. + +--- + +## Troubleshooting + +**Bot not finding `claude` binary:** +Add the path to your claude binary to the `Environment=PATH=` line in the service file. +Find it with: `which claude` + +**EnvironmentFile not found:** +Make sure your `.env` file exists at the path specified in the service file. + +**Service fails immediately:** +Check logs: `journalctl --user -u claude-telegram-bot.service -n 50` diff --git a/systemd/claude-daily-brief.service b/systemd/claude-daily-brief.service new file mode 100644 index 0000000..44e9bef --- /dev/null +++ b/systemd/claude-daily-brief.service @@ -0,0 +1,23 @@ +[Unit] +Description=Claude Code Daily Briefing +After=network-online.target +Wants=network-online.target + +[Service] +Type=oneshot + +# UPDATE: Path to claude binary +ExecStart=/usr/local/bin/claude -p "Run the daily-brief skill" \ + --allowedTools Read,WebSearch,Bash,Skill + +# UPDATE: Path to your workspace with CLAUDE.md +WorkingDirectory=/home/YOUR_USERNAME/your-workspace + +Environment=PATH=/usr/local/bin:/usr/bin:/bin:/home/YOUR_USERNAME/.local/bin + +StandardOutput=journal +StandardError=journal +SyslogIdentifier=claude-daily-brief + +[Install] +WantedBy=default.target diff --git a/systemd/claude-daily-brief.timer b/systemd/claude-daily-brief.timer new file mode 100644 index 0000000..8981750 --- /dev/null +++ b/systemd/claude-daily-brief.timer @@ -0,0 +1,14 @@ +# Daily briefing timer -- systemd equivalent of launchd StartCalendarInterval +# Runs claude-daily-brief.service every day at 7:00 AM + +[Unit] +Description=Claude Code Daily Briefing Timer +Requires=claude-daily-brief.service + +[Timer] +# Run at 7:00 AM every day +OnCalendar=*-*-* 07:00:00 +Persistent=true + +[Install] +WantedBy=timers.target diff --git a/systemd/claude-telegram-bot.service b/systemd/claude-telegram-bot.service new file mode 100644 index 0000000..11ac768 --- /dev/null +++ b/systemd/claude-telegram-bot.service @@ -0,0 +1,29 @@ +[Unit] +Description=Claude Code Telegram Bot +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple + +# UPDATE: Path to your Python and telegram-bot.py +ExecStart=/usr/bin/python3 /home/YOUR_USERNAME/claude-code-telegram/telegram-bot.py + +# UPDATE: Path to repo directory +WorkingDirectory=/home/YOUR_USERNAME/claude-code-telegram + +# Load .env file for secrets +EnvironmentFile=/home/YOUR_USERNAME/claude-code-telegram/.env + +# Keep PATH sane so 'claude' binary is found +Environment=PATH=/usr/local/bin:/usr/bin:/bin:/home/YOUR_USERNAME/.local/bin:/home/YOUR_USERNAME/.npm-global/bin + +Restart=on-failure +RestartSec=10 + +StandardOutput=journal +StandardError=journal +SyslogIdentifier=claude-telegram-bot + +[Install] +WantedBy=default.target diff --git a/telegram-bot.py b/telegram-bot.py index 0c0b660..640835c 100644 --- a/telegram-bot.py +++ b/telegram-bot.py @@ -36,12 +36,28 @@ CLAUDE_PATH = os.environ.get("CLAUDE_PATH", "claude") SESSION_FILE = Path.home() / ".telegram-claude-sessions.json" -# Optional: Voice transcription (requires mlx-whisper on Apple Silicon) -try: - import mlx_whisper - VOICE_ENABLED = True -except ImportError: - VOICE_ENABLED = False +# Optional: Voice transcription +# macOS (Apple Silicon): pip install mlx-whisper +# Linux: pip install faster-whisper +import platform +VOICE_ENABLED = False +VOICE_BACKEND = None + +if platform.system() == "Darwin": + try: + import mlx_whisper + VOICE_ENABLED = True + VOICE_BACKEND = "mlx" + except ImportError: + pass +else: + try: + from faster_whisper import WhisperModel + _whisper_model = WhisperModel("small", device="cpu", compute_type="int8") + VOICE_ENABLED = True + VOICE_BACKEND = "faster-whisper" + except ImportError: + pass def load_sessions() -> dict: @@ -55,15 +71,20 @@ def save_sessions(sessions: dict): def transcribe_audio(audio_path: str) -> str: - """Transcribe audio file using mlx-whisper (Apple Silicon only).""" + """Transcribe audio file. Uses mlx-whisper on macOS, faster-whisper on Linux.""" if not VOICE_ENABLED: return None - result = mlx_whisper.transcribe( - audio_path, - path_or_hf_repo="mlx-community/whisper-small-mlx", - verbose=False - ) - return result.get("text", "").strip() + if VOICE_BACKEND == "mlx": + result = mlx_whisper.transcribe( + audio_path, + path_or_hf_repo="mlx-community/whisper-small-mlx", + verbose=False + ) + return result.get("text", "").strip() + elif VOICE_BACKEND == "faster-whisper": + segments, _ = _whisper_model.transcribe(audio_path) + return " ".join(s.text for s in segments).strip() + return None class TelegramRenderer(mistune.HTMLRenderer): @@ -214,7 +235,11 @@ async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE): async def handle_voice(update: Update, context: ContextTypes.DEFAULT_TYPE): """Handle voice messages by transcribing and sending to Claude.""" if not VOICE_ENABLED: - await update.message.reply_text("Voice messages not supported (requires mlx-whisper on Apple Silicon)") + await update.message.reply_text( + "Voice messages not supported.\n" + "macOS: pip install mlx-whisper\n" + "Linux: pip install faster-whisper" + ) return user_id = update.effective_user.id @@ -301,9 +326,22 @@ def main(): print("Set TELEGRAM_BOT_TOKEN in .env file") return + if not ALLOWED_USERS: + print() + print("╔══════════════════════════════════════════════════════════════╗") + print("║ WARNING: ALLOWED_USERS is not set ║") + print("║ ║") + print("║ Anyone who finds your bot's username can send it messages ║") + print("║ and run Claude Code with full tool access on this machine. ║") + print("║ ║") + print("║ Set ALLOWED_USERS=your_telegram_id in your .env file. ║") + print("║ Get your ID from @userinfobot on Telegram. ║") + print("╚══════════════════════════════════════════════════════════════╝") + print() + print(f"Starting bot...") print(f"Workspace: {WORKSPACE}") - print(f"Allowed users: {ALLOWED_USERS or 'Everyone (set ALLOWED_USERS to restrict)'}") + print(f"Allowed users: {ALLOWED_USERS or 'Everyone (ALLOWED_USERS not set -- see warning above)'}") print(f"Voice enabled: {VOICE_ENABLED}") app = Application.builder().token(BOT_TOKEN).build()