ghost-code
Ghostty + Claude Code Telegram bridge. Get notified on your phone when Claude finishes a task or needs input — reply to inject text back into the terminal, or chat with Claude directly via streaming responses.
Features
- Stop hook — sends task completion summaries (silent, with expandable blockquote for long text)
- Notification hook — forwards Claude's attention requests
- PreToolUse hook — tool approval via inline Allow/Deny buttons (expandable details)
- StatusLine hook — real-time status bar showing model, cost, plan usage, context window, and tool stats
- Streaming chat — send messages to Claude from Telegram, responses stream live via
sendMessageDraft - Session reply — reply to any notification to inject text into the originating Ghostty tab, with target project confirmation to prevent misrouting
- Multi-machine support — all messages, commands, and sessions include hostname (e.g.
project@MacBook-Air) to distinguish notifications across devices - Noise filtering — suppresses noisy system notifications (quota recovery, session resumed, waiting for input)
- Sleep prevention — auto-runs
caffeinatefor 1 hour on every Telegram interaction to keep macOS awake - Single instance — flock-based PID locking ensures only one daemon runs at a time
- Graceful shutdown — signal handling (SIGINT/SIGTERM) with PID file cleanup
Single binary, no runtime dependencies.
Prerequisites
| Requirement | Version | Check |
|---|---|---|
| Rust | 1.70+ | cargo --version |
| Claude Code | any | claude --version |
| Ghostty | any | only needed for session reply |
Installation
Step 1: Create a Telegram bot
- Open Telegram and search for @BotFather
- Send
/newbotand follow the prompts to pick a name and username - BotFather replies with a bot token — save it (looks like
123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11) - Open a chat with your new bot and send any message (this initializes the chat)
- Get your chat ID by messaging @userinfobot — it replies with your numeric ID
Step 2: Install
Recommended — install from crates.io:
Then run the setup script to configure Claude Code hooks:
Alternative — install from source:
The install script builds the binary, copies it to ~/.claude/hooks/ghost-code, merges hook entries into ~/.claude/settings.json, and creates ~/.claude/hooks/ghost-code.env from the template.
Step 3: Configure
Edit ~/.claude/hooks/ghost-code.env with the token and chat ID from Step 1:
TELEGRAM_BOT_TOKEN=123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11
TELEGRAM_CHAT_ID=987654321
Values can optionally be quoted ("value" or 'value').
Step 4: Verify
You should receive two Telegram messages:
- A silent stop-hook summary
- A notification message
If nothing arrives, see Troubleshooting below.
Bot daemon
The bot daemon handles callback queries, session replies, and streaming chat. It is auto-started by hook commands, or you can run it manually:
Send any text message to chat with Claude — responses stream live using sendMessageDraft. Reply to any hook notification to inject text into the originating Ghostty terminal tab.
Bot commands
| Command | Description |
|---|---|
/help |
Show help (includes hostname) |
/sessions |
List active sessions on this machine |
/dir [path] |
Get/set working directory |
/status |
Show bot status with hostname and PID |
/stop |
Stop the bot |
Configuration
All settings can be set in ghost-code.env or as environment variables (env vars take precedence):
| Variable | Default | Description |
|---|---|---|
TELEGRAM_BOT_TOKEN |
(required) | Telegram bot token from BotFather |
TELEGRAM_CHAT_ID |
(required) | Your Telegram chat ID |
WORKING_DIR |
~/.claude/ghost-code/workspace |
Working directory for claude -p |
CLAUDE_TIMEOUT |
300 |
Execution timeout in seconds |
APPROVAL_TOOLS |
(empty) | Comma-separated tool names requiring approval |
APPROVAL_TIMEOUT |
120 |
Seconds to wait for approval response |
STATUSLINE |
true |
Enable statusline hook (see below) |
DEBUG |
false |
Log raw hook JSON to debug file |
Tool approval
To require Telegram approval before Claude executes certain tools, set APPROVAL_TOOLS:
APPROVAL_TOOLS=Bash,Write,Edit
When Claude tries to use a listed tool, you'll receive a Telegram message with Allow/Deny buttons. The hook blocks until you respond or the timeout expires (defaults to deny).
Statusline
The statusline hook adds a real-time status bar to Claude Code showing:
🤖 Opus 4.6 | 💰 $5 / $463 today | 📊 82% block · 38% weekly | 🧠 25% | 🔧 RTK 80% 3.6M | 🌐 WS 93% 3.3K | 📡 TG
| Component | Source | Description |
|---|---|---|
| 🤖 Model | Claude Code session | Current model name |
| 💰 Costs | JSONL scan | Session cost / today's total |
| 📊 Plan | Anthropic API | Block (5h) and weekly usage with reset times |
| 🧠 Context | Claude Code session | Context window usage percentage |
| 🔧 RTK | rtk gain |
Token savings from RTK (auto-detected) |
| 🌐 WS | websummary stats |
Token savings from websummary (auto-detected) |
| 📡 TG | Bot daemon | Telegram bot status |
Auto-detection: RTK and websummary stats are shown only if the respective tools are installed. If rtk or websummary commands are not found, those sections are silently omitted.
Enabling / disabling
The statusline hook is installed by default. To disable it:
- Set
STATUSLINE=falsein~/.claude/hooks/ghost-code.env - Re-run
bash install.sh
This removes the StatusLine hook from ~/.claude/settings.json. Set back to true and re-run to re-enable.
Telegram API features
This project uses modern Telegram Bot API features:
link_preview_options— replaces deprecateddisable_web_page_previewsendMessageDraft— streams partial responses as Claude generates them (Bot API 9.3+)disable_notification— stop hook sends silently (no ring)- Expandable blockquotes (
<blockquote expandable>) — long summaries and tool details are collapsible
Hook data format
The binary reads JSON from stdin as provided by Claude Code hooks:
| Hook | Key field | Description |
|---|---|---|
| Stop | last_assistant_message |
Claude's final response text |
| Notification | message, title |
What Claude needs from you |
| PreToolUse | tool_name, tool_input |
Tool about to be executed |
| StatusLine | model, cost, context_window |
Session data (piped to stdin) |
Logging
The bot and hook handlers output structured log lines to stderr with timestamps and contextual tags:
12:34:56.789 [bot] started (PID 12345)
12:34:56.790 [bot] chat_id=123456789
12:34:56.790 [bot] working_dir=~/.claude/ghost-code/workspace
12:34:57.001 [poll] received 1 update(s)
12:34:57.002 [msg] from=Peter msg_id=42 len=15: hello claude
12:34:57.003 [claude] starting: dir=~/.claude/ghost-code/workspace timeout=300s prompt=hello claude
12:34:57.050 [claude] spawned pid=67890
12:34:58.100 [claude] streaming... 256chars, 1 drafts sent
12:35:02.100 [claude] done in 5.1s (512 chars, 6 drafts) status=Ok(ExitStatus(0))
[telegram] sent msg_id=Some(43) len=512 mode=Some("HTML")
Hook handlers log with [hook:stop], [hook:notification], and [hook:pre-tool-use] tags.
Debug mode
Set DEBUG=true in the .env file (or as an env var). Raw hook JSON will be logged to ~/.claude/hooks/ghost-code.debug.log. This is in addition to the standard stderr logging described above.
Updating
If installed from source: cd ghost-code && git pull && bash install.sh
Uninstalling
Then remove the ghost-code hook entries from ~/.claude/settings.json (under hooks.Stop, hooks.Notification, hooks.PreToolUse, hooks.StatusLine).
Manual configuration
If you prefer not to use the install script, add this to ~/.claude/settings.json:
The StatusLine hook is optional — omit it if you don't want the status bar.
License
MIT