@jsayubi/ccgram 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +19 -0
- package/LICENSE +21 -0
- package/README.md +338 -0
- package/ccgram.service +24 -0
- package/config/channels.json +58 -0
- package/config/default.json +27 -0
- package/config/defaults/config.json +16 -0
- package/config/defaults/i18n.json +32 -0
- package/config/email-template.json +31 -0
- package/config/test-with-subagent.json +16 -0
- package/config/user.json +27 -0
- package/dist/claude-hook-notify.d.ts +7 -0
- package/dist/claude-hook-notify.d.ts.map +1 -0
- package/dist/claude-hook-notify.js +154 -0
- package/dist/claude-hook-notify.js.map +1 -0
- package/dist/claude-remote.d.ts +50 -0
- package/dist/claude-remote.d.ts.map +1 -0
- package/dist/claude-remote.js +927 -0
- package/dist/claude-remote.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +110 -0
- package/dist/cli.js.map +1 -0
- package/dist/enhanced-hook-notify.d.ts +16 -0
- package/dist/enhanced-hook-notify.d.ts.map +1 -0
- package/dist/enhanced-hook-notify.js +288 -0
- package/dist/enhanced-hook-notify.js.map +1 -0
- package/dist/permission-hook.d.ts +15 -0
- package/dist/permission-hook.d.ts.map +1 -0
- package/dist/permission-hook.js +357 -0
- package/dist/permission-hook.js.map +1 -0
- package/dist/prompt-bridge.d.ts +50 -0
- package/dist/prompt-bridge.d.ts.map +1 -0
- package/dist/prompt-bridge.js +173 -0
- package/dist/prompt-bridge.js.map +1 -0
- package/dist/question-notify.d.ts +16 -0
- package/dist/question-notify.d.ts.map +1 -0
- package/dist/question-notify.js +272 -0
- package/dist/question-notify.js.map +1 -0
- package/dist/setup.d.ts +10 -0
- package/dist/setup.d.ts.map +1 -0
- package/dist/setup.js +649 -0
- package/dist/setup.js.map +1 -0
- package/dist/smart-monitor.d.ts +7 -0
- package/dist/smart-monitor.d.ts.map +1 -0
- package/dist/smart-monitor.js +256 -0
- package/dist/smart-monitor.js.map +1 -0
- package/dist/src/automation/claude-automation.d.ts +45 -0
- package/dist/src/automation/claude-automation.d.ts.map +1 -0
- package/dist/src/automation/claude-automation.js +367 -0
- package/dist/src/automation/claude-automation.js.map +1 -0
- package/dist/src/automation/clipboard-automation.d.ts +35 -0
- package/dist/src/automation/clipboard-automation.d.ts.map +1 -0
- package/dist/src/automation/clipboard-automation.js +242 -0
- package/dist/src/automation/clipboard-automation.js.map +1 -0
- package/dist/src/automation/simple-automation.d.ts +56 -0
- package/dist/src/automation/simple-automation.d.ts.map +1 -0
- package/dist/src/automation/simple-automation.js +283 -0
- package/dist/src/automation/simple-automation.js.map +1 -0
- package/dist/src/channels/base/channel.d.ts +60 -0
- package/dist/src/channels/base/channel.d.ts.map +1 -0
- package/dist/src/channels/base/channel.js +96 -0
- package/dist/src/channels/base/channel.js.map +1 -0
- package/dist/src/channels/email/smtp.d.ts +74 -0
- package/dist/src/channels/email/smtp.d.ts.map +1 -0
- package/dist/src/channels/email/smtp.js +605 -0
- package/dist/src/channels/email/smtp.js.map +1 -0
- package/dist/src/channels/line/line.d.ts +36 -0
- package/dist/src/channels/line/line.d.ts.map +1 -0
- package/dist/src/channels/line/line.js +180 -0
- package/dist/src/channels/line/line.js.map +1 -0
- package/dist/src/channels/line/webhook.d.ts +55 -0
- package/dist/src/channels/line/webhook.d.ts.map +1 -0
- package/dist/src/channels/line/webhook.js +191 -0
- package/dist/src/channels/line/webhook.js.map +1 -0
- package/dist/src/channels/local/desktop.d.ts +30 -0
- package/dist/src/channels/local/desktop.d.ts.map +1 -0
- package/dist/src/channels/local/desktop.js +161 -0
- package/dist/src/channels/local/desktop.js.map +1 -0
- package/dist/src/channels/telegram/telegram.d.ts +43 -0
- package/dist/src/channels/telegram/telegram.d.ts.map +1 -0
- package/dist/src/channels/telegram/telegram.js +223 -0
- package/dist/src/channels/telegram/telegram.js.map +1 -0
- package/dist/src/channels/telegram/webhook.d.ts +75 -0
- package/dist/src/channels/telegram/webhook.d.ts.map +1 -0
- package/dist/src/channels/telegram/webhook.js +278 -0
- package/dist/src/channels/telegram/webhook.js.map +1 -0
- package/dist/src/config-manager.d.ts +16 -0
- package/dist/src/config-manager.d.ts.map +1 -0
- package/dist/src/config-manager.js +152 -0
- package/dist/src/config-manager.js.map +1 -0
- package/dist/src/core/config.d.ts +28 -0
- package/dist/src/core/config.d.ts.map +1 -0
- package/dist/src/core/config.js +248 -0
- package/dist/src/core/config.js.map +1 -0
- package/dist/src/core/logger.d.ts +19 -0
- package/dist/src/core/logger.d.ts.map +1 -0
- package/dist/src/core/logger.js +47 -0
- package/dist/src/core/logger.js.map +1 -0
- package/dist/src/core/notifier.d.ts +45 -0
- package/dist/src/core/notifier.d.ts.map +1 -0
- package/dist/src/core/notifier.js +189 -0
- package/dist/src/core/notifier.js.map +1 -0
- package/dist/src/daemon/taskping-daemon.d.ts +38 -0
- package/dist/src/daemon/taskping-daemon.d.ts.map +1 -0
- package/dist/src/daemon/taskping-daemon.js +306 -0
- package/dist/src/daemon/taskping-daemon.js.map +1 -0
- package/dist/src/relay/claude-command-bridge.d.ts +57 -0
- package/dist/src/relay/claude-command-bridge.d.ts.map +1 -0
- package/dist/src/relay/claude-command-bridge.js +188 -0
- package/dist/src/relay/claude-command-bridge.js.map +1 -0
- package/dist/src/relay/command-relay.d.ts +94 -0
- package/dist/src/relay/command-relay.d.ts.map +1 -0
- package/dist/src/relay/command-relay.js +463 -0
- package/dist/src/relay/command-relay.js.map +1 -0
- package/dist/src/relay/email-listener.d.ts +65 -0
- package/dist/src/relay/email-listener.d.ts.map +1 -0
- package/dist/src/relay/email-listener.js +460 -0
- package/dist/src/relay/email-listener.js.map +1 -0
- package/dist/src/relay/relay-pty.d.ts +21 -0
- package/dist/src/relay/relay-pty.d.ts.map +1 -0
- package/dist/src/relay/relay-pty.js +696 -0
- package/dist/src/relay/relay-pty.js.map +1 -0
- package/dist/src/relay/smart-injector.d.ts +30 -0
- package/dist/src/relay/smart-injector.d.ts.map +1 -0
- package/dist/src/relay/smart-injector.js +233 -0
- package/dist/src/relay/smart-injector.js.map +1 -0
- package/dist/src/relay/tmux-injector.d.ts +46 -0
- package/dist/src/relay/tmux-injector.d.ts.map +1 -0
- package/dist/src/relay/tmux-injector.js +413 -0
- package/dist/src/relay/tmux-injector.js.map +1 -0
- package/dist/src/tools/config-manager.d.ts +33 -0
- package/dist/src/tools/config-manager.d.ts.map +1 -0
- package/dist/src/tools/config-manager.js +448 -0
- package/dist/src/tools/config-manager.js.map +1 -0
- package/dist/src/tools/installer.d.ts +38 -0
- package/dist/src/tools/installer.d.ts.map +1 -0
- package/dist/src/tools/installer.js +222 -0
- package/dist/src/tools/installer.js.map +1 -0
- package/dist/src/types/callbacks.d.ts +29 -0
- package/dist/src/types/callbacks.d.ts.map +1 -0
- package/dist/src/types/callbacks.js +7 -0
- package/dist/src/types/callbacks.js.map +1 -0
- package/dist/src/types/config.d.ts +56 -0
- package/dist/src/types/config.d.ts.map +1 -0
- package/dist/src/types/config.js +6 -0
- package/dist/src/types/config.js.map +1 -0
- package/dist/src/types/hooks.d.ts +47 -0
- package/dist/src/types/hooks.d.ts.map +1 -0
- package/dist/src/types/hooks.js +6 -0
- package/dist/src/types/hooks.js.map +1 -0
- package/dist/src/types/index.d.ts +7 -0
- package/dist/src/types/index.d.ts.map +1 -0
- package/dist/src/types/index.js +23 -0
- package/dist/src/types/index.js.map +1 -0
- package/dist/src/types/ipc.d.ts +43 -0
- package/dist/src/types/ipc.d.ts.map +1 -0
- package/dist/src/types/ipc.js +7 -0
- package/dist/src/types/ipc.js.map +1 -0
- package/dist/src/types/session.d.ts +70 -0
- package/dist/src/types/session.d.ts.map +1 -0
- package/dist/src/types/session.js +9 -0
- package/dist/src/types/session.js.map +1 -0
- package/dist/src/types/telegram.d.ts +58 -0
- package/dist/src/types/telegram.d.ts.map +1 -0
- package/dist/src/types/telegram.js +6 -0
- package/dist/src/types/telegram.js.map +1 -0
- package/dist/src/utils/active-check.d.ts +19 -0
- package/dist/src/utils/active-check.d.ts.map +1 -0
- package/dist/src/utils/active-check.js +41 -0
- package/dist/src/utils/active-check.js.map +1 -0
- package/dist/src/utils/callback-parser.d.ts +21 -0
- package/dist/src/utils/callback-parser.d.ts.map +1 -0
- package/dist/src/utils/callback-parser.js +58 -0
- package/dist/src/utils/callback-parser.js.map +1 -0
- package/dist/src/utils/controller-injector.d.ts +21 -0
- package/dist/src/utils/controller-injector.d.ts.map +1 -0
- package/dist/src/utils/controller-injector.js +108 -0
- package/dist/src/utils/controller-injector.js.map +1 -0
- package/dist/src/utils/conversation-tracker.d.ts +32 -0
- package/dist/src/utils/conversation-tracker.d.ts.map +1 -0
- package/dist/src/utils/conversation-tracker.js +119 -0
- package/dist/src/utils/conversation-tracker.js.map +1 -0
- package/dist/src/utils/http-request.d.ts +25 -0
- package/dist/src/utils/http-request.d.ts.map +1 -0
- package/dist/src/utils/http-request.js +66 -0
- package/dist/src/utils/http-request.js.map +1 -0
- package/dist/src/utils/optional-require.d.ts +13 -0
- package/dist/src/utils/optional-require.d.ts.map +1 -0
- package/dist/src/utils/optional-require.js +37 -0
- package/dist/src/utils/optional-require.js.map +1 -0
- package/dist/src/utils/paths.d.ts +11 -0
- package/dist/src/utils/paths.d.ts.map +1 -0
- package/dist/src/utils/paths.js +28 -0
- package/dist/src/utils/paths.js.map +1 -0
- package/dist/src/utils/pty-session-manager.d.ts +42 -0
- package/dist/src/utils/pty-session-manager.d.ts.map +1 -0
- package/dist/src/utils/pty-session-manager.js +182 -0
- package/dist/src/utils/pty-session-manager.js.map +1 -0
- package/dist/src/utils/subagent-tracker.d.ts +64 -0
- package/dist/src/utils/subagent-tracker.d.ts.map +1 -0
- package/dist/src/utils/subagent-tracker.js +191 -0
- package/dist/src/utils/subagent-tracker.js.map +1 -0
- package/dist/src/utils/tmux-monitor.d.ts +102 -0
- package/dist/src/utils/tmux-monitor.d.ts.map +1 -0
- package/dist/src/utils/tmux-monitor.js +642 -0
- package/dist/src/utils/tmux-monitor.js.map +1 -0
- package/dist/src/utils/trace-capture.d.ts +42 -0
- package/dist/src/utils/trace-capture.d.ts.map +1 -0
- package/dist/src/utils/trace-capture.js +102 -0
- package/dist/src/utils/trace-capture.js.map +1 -0
- package/dist/start-all-webhooks.d.ts +7 -0
- package/dist/start-all-webhooks.d.ts.map +1 -0
- package/dist/start-all-webhooks.js +98 -0
- package/dist/start-all-webhooks.js.map +1 -0
- package/dist/start-line-webhook.d.ts +7 -0
- package/dist/start-line-webhook.d.ts.map +1 -0
- package/dist/start-line-webhook.js +59 -0
- package/dist/start-line-webhook.js.map +1 -0
- package/dist/start-relay-pty.d.ts +7 -0
- package/dist/start-relay-pty.d.ts.map +1 -0
- package/dist/start-relay-pty.js +173 -0
- package/dist/start-relay-pty.js.map +1 -0
- package/dist/start-telegram-webhook.d.ts +7 -0
- package/dist/start-telegram-webhook.d.ts.map +1 -0
- package/dist/start-telegram-webhook.js +80 -0
- package/dist/start-telegram-webhook.js.map +1 -0
- package/dist/user-prompt-hook.d.ts +13 -0
- package/dist/user-prompt-hook.d.ts.map +1 -0
- package/dist/user-prompt-hook.js +45 -0
- package/dist/user-prompt-hook.js.map +1 -0
- package/dist/workspace-router.d.ts +78 -0
- package/dist/workspace-router.d.ts.map +1 -0
- package/dist/workspace-router.js +408 -0
- package/dist/workspace-router.js.map +1 -0
- package/dist/workspace-telegram-bot.d.ts +3 -0
- package/dist/workspace-telegram-bot.d.ts.map +1 -0
- package/dist/workspace-telegram-bot.js +1172 -0
- package/dist/workspace-telegram-bot.js.map +1 -0
- package/package.json +80 -0
- package/src/types/callbacks.ts +39 -0
- package/src/types/config.ts +63 -0
- package/src/types/hooks.ts +50 -0
- package/src/types/index.ts +6 -0
- package/src/types/ipc.ts +55 -0
- package/src/types/session.ts +72 -0
- package/src/types/telegram.ts +66 -0
package/.env.example
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# CCGram Configuration
|
|
2
|
+
# Copy this file to .env and fill in your values
|
|
3
|
+
|
|
4
|
+
# Required: Get your bot token from @BotFather (https://t.me/BotFather)
|
|
5
|
+
TELEGRAM_BOT_TOKEN=
|
|
6
|
+
|
|
7
|
+
# Required: Get your chat ID from @userinfobot (https://t.me/userinfobot)
|
|
8
|
+
TELEGRAM_CHAT_ID=
|
|
9
|
+
|
|
10
|
+
# Optional: Log level (debug, info, warn, error)
|
|
11
|
+
# LOG_LEVEL=info
|
|
12
|
+
|
|
13
|
+
# Project directories to scan for /new command (comma-separated)
|
|
14
|
+
# PROJECT_DIRS=~/projects,~/tools
|
|
15
|
+
|
|
16
|
+
# Suppress Telegram notifications when actively at terminal (seconds, default: 300)
|
|
17
|
+
# ACTIVE_THRESHOLD_SECONDS=300
|
|
18
|
+
|
|
19
|
+
# For advanced options (email relay, LINE, webhooks), see .env.full-example
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 JessyTsui
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# CCGram
|
|
4
|
+
|
|
5
|
+
**Control Claude Code from Telegram — approve permissions, answer questions, and manage AI sessions from your phone.**
|
|
6
|
+
|
|
7
|
+
[](https://github.com/jsayubi/ccgram/actions/workflows/ci.yml)
|
|
8
|
+
[](https://www.npmjs.com/package/@jsayubi/ccgram)
|
|
9
|
+
[](https://nodejs.org)
|
|
10
|
+
[](LICENSE)
|
|
11
|
+
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
CCGram is a self-hosted Telegram bot that bridges Claude Code to your phone. When Claude needs a permission, has a question, or finishes a task — you get a Telegram message with inline buttons to respond. You never need to be at your keyboard.
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
Claude Code → ccgram hooks → Telegram bot → 📱 your phone
|
|
20
|
+
↑ ↓
|
|
21
|
+
└──── tmux or PTY injection ─────┘
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Features
|
|
25
|
+
|
|
26
|
+
- **Permission approvals** — Allow, Deny, or Always allow with a single tap
|
|
27
|
+
- **Question answering** — Select from Claude's options via inline buttons (single and multi-select)
|
|
28
|
+
- **Smart notifications** — Task completions, session start/end, and subagent activity — silent when you're at your terminal, instant when you're away
|
|
29
|
+
- **Remote command routing** — Send any command to any Claude session from Telegram
|
|
30
|
+
- **Session management** — List, switch between, and interrupt active sessions
|
|
31
|
+
- **Project launcher** — Start Claude in any project directory with `/new myproject`
|
|
32
|
+
- **Smart routing** — Prefix matching, default workspace, reply-to routing
|
|
33
|
+
- **Typing indicator** — See when the bot is waiting for Claude to respond
|
|
34
|
+
- **tmux optional** — Falls back to a headless PTY session (`node-pty`) when tmux is unavailable
|
|
35
|
+
- **One-command setup** — Interactive wizard installs hooks, generates service file, starts bot
|
|
36
|
+
|
|
37
|
+
## Requirements
|
|
38
|
+
|
|
39
|
+
- [Node.js](https://nodejs.org) 18+
|
|
40
|
+
- A Telegram bot token (from [@BotFather](https://t.me/BotFather))
|
|
41
|
+
- Your Telegram chat ID (from [@userinfobot](https://t.me/userinfobot))
|
|
42
|
+
- [tmux](https://github.com/tmux/tmux/wiki) _(optional — falls back to headless PTY via `node-pty` when absent)_
|
|
43
|
+
|
|
44
|
+
## Quick Start
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
npx @jsayubi/ccgram init
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The setup wizard will:
|
|
51
|
+
1. Ask for your bot token and chat ID
|
|
52
|
+
2. Install the bot to `~/.ccgram/`
|
|
53
|
+
3. Merge the required hooks into `~/.claude/settings.json`
|
|
54
|
+
4. Generate and start a background service (launchd on macOS, systemd on Linux)
|
|
55
|
+
|
|
56
|
+
Then open Telegram and message your bot — Claude Code will now notify you remotely.
|
|
57
|
+
|
|
58
|
+
## How It Works
|
|
59
|
+
|
|
60
|
+
CCGram integrates with [Claude Code hooks](https://docs.anthropic.com/en/docs/claude-code/hooks) — shell scripts that Claude Code calls at key moments. Each hook script sends a Telegram message and, when you respond, injects keystrokes back into the tmux session running Claude.
|
|
61
|
+
|
|
62
|
+
### Hooks installed
|
|
63
|
+
|
|
64
|
+
| Hook | Event | What it does |
|
|
65
|
+
|------|-------|-------------|
|
|
66
|
+
| `permission-hook.js` | `PermissionRequest` | Sends a permission dialog with Allow / Deny / Always buttons. Blocks Claude until you respond. |
|
|
67
|
+
| `question-notify.js` | `PreToolUse` (AskUserQuestion) | Sends Claude's question with selectable options. Your tap injects the selection via tmux/PTY. |
|
|
68
|
+
| `enhanced-hook-notify.js completed` | `Stop` | Notifies you when Claude finishes a task, including the last response text. |
|
|
69
|
+
| `enhanced-hook-notify.js waiting` | `Notification` | Notifies you when Claude is waiting for input. |
|
|
70
|
+
| `user-prompt-hook.js` | `UserPromptSubmit` | Tracks terminal activity so notifications are suppressed when you're actively working. |
|
|
71
|
+
| `enhanced-hook-notify.js session-start` | `SessionStart` | Notifies you when a new Claude session starts. |
|
|
72
|
+
| `enhanced-hook-notify.js session-end` | `SessionEnd` | Notifies you when a Claude session ends, with the final response. |
|
|
73
|
+
| `enhanced-hook-notify.js subagent-done` | `SubagentStop` | Notifies you when a subagent task completes. |
|
|
74
|
+
|
|
75
|
+
> **Smart suppression** — all notifications (including permissions) are automatically silenced when you've sent a message to Claude within the last 5 minutes. The moment you step away, Telegram takes over. Telegram-injected commands always get their response back to Telegram regardless.
|
|
76
|
+
|
|
77
|
+
### Permission flow
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
Claude requests permission
|
|
81
|
+
→ hook generates promptId, writes pending file
|
|
82
|
+
→ Telegram message with inline buttons sent to your phone
|
|
83
|
+
→ you tap Allow / Deny
|
|
84
|
+
→ bot writes response file
|
|
85
|
+
→ hook reads response, returns decision to Claude
|
|
86
|
+
→ Claude continues
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Question flow
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
Claude asks a question (AskUserQuestion)
|
|
93
|
+
→ Claude shows question UI in terminal
|
|
94
|
+
→ question-notify sends options to Telegram
|
|
95
|
+
→ you tap an option
|
|
96
|
+
→ bot injects arrow keys + Enter via tmux or PTY
|
|
97
|
+
→ Claude's question UI captures the keystrokes
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Bot Commands
|
|
101
|
+
|
|
102
|
+
### Session management
|
|
103
|
+
|
|
104
|
+
| Command | Description |
|
|
105
|
+
|---------|-------------|
|
|
106
|
+
| `/sessions` | List all active Claude sessions with status and age |
|
|
107
|
+
| `/use <workspace>` | Set default workspace — plain text messages route there |
|
|
108
|
+
| `/use` | Show current default workspace |
|
|
109
|
+
| `/use clear` | Clear the default workspace |
|
|
110
|
+
|
|
111
|
+
### Workspace control
|
|
112
|
+
|
|
113
|
+
| Command | Description |
|
|
114
|
+
|---------|-------------|
|
|
115
|
+
| `/<workspace> <command>` | Send a command to a specific Claude session |
|
|
116
|
+
| `/status [workspace]` | Show the last 20 lines of tmux pane output |
|
|
117
|
+
| `/stop [workspace]` | Send Ctrl+C to interrupt the running prompt |
|
|
118
|
+
| `/compact [workspace]` | Run `/compact` and wait for it to complete |
|
|
119
|
+
|
|
120
|
+
### Project launcher
|
|
121
|
+
|
|
122
|
+
| Command | Description |
|
|
123
|
+
|---------|-------------|
|
|
124
|
+
| `/new` | Show recent projects as buttons |
|
|
125
|
+
| `/new myproject` | Start Claude in `~/projects/myproject` (or wherever it's found) |
|
|
126
|
+
|
|
127
|
+
The `/new` command searches your configured `PROJECT_DIRS`, finds exact or prefix-matched directories, creates a tmux session (or PTY session if tmux is unavailable), starts Claude, and sets it as the default workspace.
|
|
128
|
+
|
|
129
|
+
### Smart routing
|
|
130
|
+
|
|
131
|
+
**Prefix matching** — workspace names can be abbreviated. `/ass hello` routes to `assistant` if it's unique. Ambiguous prefixes show a list to choose from.
|
|
132
|
+
|
|
133
|
+
**Reply-to routing** — reply to any bot notification (permission, question, or status message) to route your reply to that workspace.
|
|
134
|
+
|
|
135
|
+
**Default workspace** — after `/use myproject`, plain text messages route there automatically.
|
|
136
|
+
|
|
137
|
+
## Configuration
|
|
138
|
+
|
|
139
|
+
CCGram is configured via `~/.ccgram/.env`. Run `ccgram init` to generate it interactively, or edit it manually:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
# Required
|
|
143
|
+
TELEGRAM_ENABLED=true
|
|
144
|
+
TELEGRAM_BOT_TOKEN=your_bot_token_here
|
|
145
|
+
TELEGRAM_CHAT_ID=your_chat_id_here
|
|
146
|
+
|
|
147
|
+
# Project directories to scan (for /new command and session listing)
|
|
148
|
+
PROJECT_DIRS=~/projects,~/tools
|
|
149
|
+
|
|
150
|
+
# Suppress notifications when you're actively at the terminal
|
|
151
|
+
# Default: 300 seconds (5 minutes). Set to 0 to always notify.
|
|
152
|
+
ACTIVE_THRESHOLD_SECONDS=300
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Advanced options
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
# Allow only specific Telegram user IDs (comma-separated)
|
|
159
|
+
TELEGRAM_WHITELIST=123456789,987654321
|
|
160
|
+
|
|
161
|
+
# Use webhooks instead of long-polling (requires public URL)
|
|
162
|
+
TELEGRAM_WEBHOOK_URL=https://example.com/webhook
|
|
163
|
+
TELEGRAM_WEBHOOK_PORT=3001
|
|
164
|
+
|
|
165
|
+
# Force IPv4 for Telegram API (useful on some VPS providers)
|
|
166
|
+
TELEGRAM_FORCE_IPV4=false
|
|
167
|
+
|
|
168
|
+
# Tmux keystroke injection mode
|
|
169
|
+
INJECTION_MODE=tmux # tmux (default) or pty
|
|
170
|
+
|
|
171
|
+
# Custom session map path
|
|
172
|
+
SESSION_MAP_PATH=~/.ccgram/src/data/session-map.json
|
|
173
|
+
|
|
174
|
+
# Logging
|
|
175
|
+
LOG_LEVEL=info # debug, info, warn, error
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Service Management
|
|
179
|
+
|
|
180
|
+
`ccgram init` generates and starts a background service automatically.
|
|
181
|
+
|
|
182
|
+
### macOS (launchd)
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
# Restart
|
|
186
|
+
launchctl kickstart -k gui/$(id -u)/com.ccgram
|
|
187
|
+
|
|
188
|
+
# Stop / Start
|
|
189
|
+
launchctl stop com.ccgram
|
|
190
|
+
launchctl start com.ccgram
|
|
191
|
+
|
|
192
|
+
# Logs
|
|
193
|
+
tail -f ~/.ccgram/logs/bot-stdout.log
|
|
194
|
+
tail -f ~/.ccgram/logs/bot-stderr.log
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Linux (systemd)
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
sudo systemctl status ccgram
|
|
201
|
+
sudo systemctl restart ccgram
|
|
202
|
+
journalctl -u ccgram -f
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## Installation Details
|
|
206
|
+
|
|
207
|
+
`ccgram init` installs the bot to `~/.ccgram/` — a persistent directory that survives `npx` cleanup and system updates. The hooks in `~/.claude/settings.json` always point to this location.
|
|
208
|
+
|
|
209
|
+
```
|
|
210
|
+
~/.ccgram/
|
|
211
|
+
├── dist/ # Compiled JavaScript (hook scripts + bot)
|
|
212
|
+
├── config/ # Default config templates
|
|
213
|
+
├── src/data/
|
|
214
|
+
│ ├── session-map.json # Workspace → tmux session mapping
|
|
215
|
+
│ ├── default-workspace.json
|
|
216
|
+
│ ├── project-history.json # Recent projects for /new
|
|
217
|
+
│ └── message-workspace-map.json # reply-to routing (24h TTL)
|
|
218
|
+
├── logs/
|
|
219
|
+
│ ├── bot-stdout.log
|
|
220
|
+
│ └── bot-stderr.log
|
|
221
|
+
└── .env # Your configuration
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
## Development
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
git clone https://github.com/jsayubi/ccgram
|
|
228
|
+
cd ccgram
|
|
229
|
+
npm install
|
|
230
|
+
cp .env.example .env # Add your bot token and chat ID
|
|
231
|
+
npm run build
|
|
232
|
+
node dist/workspace-telegram-bot.js
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
npm run build # Compile TypeScript → dist/
|
|
237
|
+
npm run build:watch # Watch mode
|
|
238
|
+
npm test # Run 57 tests (vitest)
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
**Note:** Claude Code hooks run from `~/.ccgram/dist/`, not the repo's `dist/`. After changing hook scripts during development, sync them:
|
|
242
|
+
|
|
243
|
+
```bash
|
|
244
|
+
cp -r dist/ ~/.ccgram/dist/
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
End users don't need this — `ccgram init` handles it automatically.
|
|
248
|
+
|
|
249
|
+
### Architecture
|
|
250
|
+
|
|
251
|
+
```
|
|
252
|
+
src/
|
|
253
|
+
├── utils/
|
|
254
|
+
│ ├── active-check.ts # Detect terminal activity; suppress notifications when present
|
|
255
|
+
│ ├── pty-session-manager.ts # Headless PTY backend via node-pty (tmux fallback)
|
|
256
|
+
│ ├── callback-parser.ts # Parse Telegram callback_data strings
|
|
257
|
+
│ ├── http-request.ts # Lightweight HTTPS wrapper (no axios)
|
|
258
|
+
│ ├── optional-require.ts # Graceful optional dependency loading
|
|
259
|
+
│ └── paths.ts # PROJECT_ROOT + CCGRAM_HOME constants
|
|
260
|
+
├── types/ # TypeScript interfaces
|
|
261
|
+
└── data/ # Runtime data (session map, history)
|
|
262
|
+
|
|
263
|
+
workspace-telegram-bot.ts # Main bot (long-polling, routing, callbacks)
|
|
264
|
+
workspace-router.ts # Session map, prefix matching, default workspace
|
|
265
|
+
prompt-bridge.ts # File-based IPC via /tmp/claude-prompts/
|
|
266
|
+
permission-hook.ts # Blocking permission approval hook
|
|
267
|
+
question-notify.ts # Non-blocking question notification hook
|
|
268
|
+
enhanced-hook-notify.ts # Status notification hook (Stop, Notification, SessionStart, SessionEnd, SubagentStop)
|
|
269
|
+
user-prompt-hook.ts # UserPromptSubmit hook — writes terminal activity timestamp
|
|
270
|
+
setup.ts # Interactive setup wizard
|
|
271
|
+
cli.ts # ccgram CLI entry point
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Tests
|
|
275
|
+
|
|
276
|
+
```
|
|
277
|
+
test/
|
|
278
|
+
├── prompt-bridge.test.js # 15 tests — IPC write/read/update/clean/expiry
|
|
279
|
+
├── workspace-router.test.js # 28 tests — session map, prefix matching, defaults, reply-to
|
|
280
|
+
└── callback-parser.test.js # 14 tests — all callback_data formats
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
Tests use isolated temp directories and run with `npm test` (vitest, no configuration needed).
|
|
284
|
+
|
|
285
|
+
### Dependencies
|
|
286
|
+
|
|
287
|
+
**Core:** Only `dotenv` is required. The bot runs on Node.js built-ins.
|
|
288
|
+
|
|
289
|
+
**Optional** (graceful degradation if missing):
|
|
290
|
+
- `express` — webhook servers
|
|
291
|
+
- `node-pty` — PTY relay mode
|
|
292
|
+
- `nodemailer`, `node-imap`, `mailparser` — email relay
|
|
293
|
+
- `pino`, `pino-pretty` — structured logging (falls back to console)
|
|
294
|
+
|
|
295
|
+
## FAQ
|
|
296
|
+
|
|
297
|
+
**Do I need a public server?**
|
|
298
|
+
No. CCGram uses Telegram's long-polling API — it works behind NAT, on a laptop, or anywhere with outbound HTTPS.
|
|
299
|
+
|
|
300
|
+
**What if I'm already at my terminal?**
|
|
301
|
+
All notifications — including permission requests — are suppressed automatically when you've sent a message to Claude within the last 5 minutes. The threshold is configurable via `ACTIVE_THRESHOLD_SECONDS`. Step away for more than 5 minutes and Telegram instantly takes over.
|
|
302
|
+
|
|
303
|
+
**Can I use it with multiple projects at once?**
|
|
304
|
+
Yes. Each Claude session maps to a named tmux or PTY session. Use `/sessions` to see all active sessions, or `/use <workspace>` to set a default for plain text routing.
|
|
305
|
+
|
|
306
|
+
**Do I need tmux?**
|
|
307
|
+
No. When tmux is not detected, CCGram automatically falls back to headless PTY sessions powered by [`node-pty`](https://github.com/microsoft/node-pty). No configuration required — it activates on its own.
|
|
308
|
+
|
|
309
|
+
To use PTY mode:
|
|
310
|
+
1. Install the optional dependency: `npm install node-pty` inside `~/.ccgram/`
|
|
311
|
+
2. PTY activates automatically when `tmux` is not running, or force it explicitly:
|
|
312
|
+
```bash
|
|
313
|
+
# in ~/.ccgram/.env
|
|
314
|
+
INJECTION_MODE=pty
|
|
315
|
+
```
|
|
316
|
+
3. Restart the bot: `launchctl kickstart -k gui/$(id -u)/com.ccgram` (macOS) or `sudo systemctl restart ccgram` (Linux)
|
|
317
|
+
|
|
318
|
+
Full remote control — permission approvals, question answering, `/new`, `/stop` — works identically in both modes.
|
|
319
|
+
|
|
320
|
+
**Is my bot token stored securely?**
|
|
321
|
+
The token is stored in `~/.ccgram/.env`, readable only by your user. It's never logged or transmitted beyond Telegram's API.
|
|
322
|
+
|
|
323
|
+
**What's the 64-byte callback limit?**
|
|
324
|
+
Telegram limits inline button callback data to 64 bytes. CCGram uses a compact `type:promptId:action` format to stay within this limit.
|
|
325
|
+
|
|
326
|
+
## License
|
|
327
|
+
|
|
328
|
+
MIT — see [LICENSE](LICENSE).
|
|
329
|
+
|
|
330
|
+
---
|
|
331
|
+
|
|
332
|
+
<div align="center">
|
|
333
|
+
|
|
334
|
+
Built for developers who let Claude Code run overnight and want to stay in control from anywhere.
|
|
335
|
+
|
|
336
|
+
[Report a bug](https://github.com/jsayubi/ccgram/issues) · [Request a feature](https://github.com/jsayubi/ccgram/issues)
|
|
337
|
+
|
|
338
|
+
</div>
|
package/ccgram.service
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# CCGram systemd unit file
|
|
2
|
+
# Copy to /etc/systemd/system/ccgram.service and edit paths, then:
|
|
3
|
+
# sudo systemctl daemon-reload
|
|
4
|
+
# sudo systemctl enable ccgram
|
|
5
|
+
# sudo systemctl start ccgram
|
|
6
|
+
|
|
7
|
+
[Unit]
|
|
8
|
+
Description=CCGram - Claude Code Telegram Bot
|
|
9
|
+
After=network.target
|
|
10
|
+
|
|
11
|
+
[Service]
|
|
12
|
+
Type=simple
|
|
13
|
+
User=YOUR_USERNAME
|
|
14
|
+
WorkingDirectory=/home/YOUR_USERNAME/tools/ccgram
|
|
15
|
+
ExecStart=/usr/bin/node dist/workspace-telegram-bot.js
|
|
16
|
+
Restart=always
|
|
17
|
+
RestartSec=5
|
|
18
|
+
Environment=NODE_ENV=production
|
|
19
|
+
|
|
20
|
+
StandardOutput=append:/home/YOUR_USERNAME/tools/ccgram/logs/bot-stdout.log
|
|
21
|
+
StandardError=append:/home/YOUR_USERNAME/tools/ccgram/logs/bot-stderr.log
|
|
22
|
+
|
|
23
|
+
[Install]
|
|
24
|
+
WantedBy=multi-user.target
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"desktop": {
|
|
3
|
+
"type": "local",
|
|
4
|
+
"enabled": true,
|
|
5
|
+
"config": {}
|
|
6
|
+
},
|
|
7
|
+
"email": {
|
|
8
|
+
"type": "email",
|
|
9
|
+
"enabled": true
|
|
10
|
+
},
|
|
11
|
+
"discord": {
|
|
12
|
+
"type": "chat",
|
|
13
|
+
"enabled": false,
|
|
14
|
+
"config": {
|
|
15
|
+
"webhook": "",
|
|
16
|
+
"username": "CCGram",
|
|
17
|
+
"avatar": null
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"telegram": {
|
|
21
|
+
"type": "chat",
|
|
22
|
+
"enabled": true,
|
|
23
|
+
"config": {
|
|
24
|
+
"botToken": "",
|
|
25
|
+
"chatId": "",
|
|
26
|
+
"groupId": "",
|
|
27
|
+
"whitelist": [],
|
|
28
|
+
"forceIPv4": false
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"whatsapp": {
|
|
32
|
+
"type": "chat",
|
|
33
|
+
"enabled": false,
|
|
34
|
+
"config": {
|
|
35
|
+
"webhook": "",
|
|
36
|
+
"apiKey": ""
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"feishu": {
|
|
40
|
+
"type": "chat",
|
|
41
|
+
"enabled": false,
|
|
42
|
+
"config": {
|
|
43
|
+
"webhook": "",
|
|
44
|
+
"secret": ""
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"line": {
|
|
48
|
+
"type": "chat",
|
|
49
|
+
"enabled": true,
|
|
50
|
+
"config": {
|
|
51
|
+
"channelAccessToken": "",
|
|
52
|
+
"channelSecret": "",
|
|
53
|
+
"userId": "",
|
|
54
|
+
"groupId": "",
|
|
55
|
+
"whitelist": []
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"language": "en",
|
|
3
|
+
"sound": {
|
|
4
|
+
"completed": "Glass",
|
|
5
|
+
"waiting": "Tink"
|
|
6
|
+
},
|
|
7
|
+
"enabled": true,
|
|
8
|
+
"timeout": 5,
|
|
9
|
+
"customMessages": {
|
|
10
|
+
"completed": null,
|
|
11
|
+
"waiting": null
|
|
12
|
+
},
|
|
13
|
+
"channels": {
|
|
14
|
+
"desktop": {
|
|
15
|
+
"enabled": true,
|
|
16
|
+
"priority": 1
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"relay": {
|
|
20
|
+
"enabled": false,
|
|
21
|
+
"port": 3000,
|
|
22
|
+
"auth": {
|
|
23
|
+
"enabled": false,
|
|
24
|
+
"token": null
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"language": "en",
|
|
3
|
+
"sound": {
|
|
4
|
+
"completed": "Submarine",
|
|
5
|
+
"waiting": "Hero"
|
|
6
|
+
},
|
|
7
|
+
"enabled": true,
|
|
8
|
+
"enableSubagentNotifications": false,
|
|
9
|
+
"showSubagentActivitiesInEmail": false,
|
|
10
|
+
"subagentActivityDetail": "medium",
|
|
11
|
+
"timeout": 5,
|
|
12
|
+
"customMessages": {
|
|
13
|
+
"completed": null,
|
|
14
|
+
"waiting": null
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"zh-CN": {
|
|
3
|
+
"completed": {
|
|
4
|
+
"title": "Claude Code - Task Completed",
|
|
5
|
+
"message": "[{project}] Task completed, Claude is waiting for next instruction"
|
|
6
|
+
},
|
|
7
|
+
"waiting": {
|
|
8
|
+
"title": "Claude Code - Waiting for Input",
|
|
9
|
+
"message": "[{project}] Claude needs your further guidance"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"en": {
|
|
13
|
+
"completed": {
|
|
14
|
+
"title": "Claude Code - Task Completed",
|
|
15
|
+
"message": "[{project}] Task completed, Claude is waiting for next instruction"
|
|
16
|
+
},
|
|
17
|
+
"waiting": {
|
|
18
|
+
"title": "Claude Code - Waiting for Input",
|
|
19
|
+
"message": "[{project}] Claude needs your further guidance"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"ja": {
|
|
23
|
+
"completed": {
|
|
24
|
+
"title": "Claude Code - Task Completed",
|
|
25
|
+
"message": "[{project}] Task completed, Claude is waiting for next instruction"
|
|
26
|
+
},
|
|
27
|
+
"waiting": {
|
|
28
|
+
"title": "Claude Code - Waiting for Input",
|
|
29
|
+
"message": "[{project}] Claude needs your further guidance"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"email": {
|
|
3
|
+
"type": "email",
|
|
4
|
+
"enabled": true,
|
|
5
|
+
"config": {
|
|
6
|
+
"smtp": {
|
|
7
|
+
"host": "smtp.gmail.com",
|
|
8
|
+
"port": 587,
|
|
9
|
+
"secure": false,
|
|
10
|
+
"auth": {
|
|
11
|
+
"user": "your-email@gmail.com",
|
|
12
|
+
"pass": "your-app-password"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"imap": {
|
|
16
|
+
"host": "imap.gmail.com",
|
|
17
|
+
"port": 993,
|
|
18
|
+
"secure": true,
|
|
19
|
+
"auth": {
|
|
20
|
+
"user": "your-email@gmail.com",
|
|
21
|
+
"pass": "your-app-password"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"from": "CCGram <your-email@gmail.com>",
|
|
25
|
+
"to": "your-email@gmail.com",
|
|
26
|
+
"template": {
|
|
27
|
+
"checkInterval": 30
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"language": "en",
|
|
3
|
+
"sound": {
|
|
4
|
+
"completed": "Submarine",
|
|
5
|
+
"waiting": "Hero"
|
|
6
|
+
},
|
|
7
|
+
"enabled": true,
|
|
8
|
+
"enableSubagentNotifications": false,
|
|
9
|
+
"showSubagentActivitiesInEmail": true,
|
|
10
|
+
"subagentActivityDetail": "medium",
|
|
11
|
+
"timeout": 5,
|
|
12
|
+
"customMessages": {
|
|
13
|
+
"completed": null,
|
|
14
|
+
"waiting": null
|
|
15
|
+
}
|
|
16
|
+
}
|
package/config/user.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"language": "en",
|
|
3
|
+
"sound": {
|
|
4
|
+
"completed": "Submarine",
|
|
5
|
+
"waiting": "Hero"
|
|
6
|
+
},
|
|
7
|
+
"enabled": true,
|
|
8
|
+
"timeout": 5,
|
|
9
|
+
"customMessages": {
|
|
10
|
+
"completed": null,
|
|
11
|
+
"waiting": null
|
|
12
|
+
},
|
|
13
|
+
"channels": {
|
|
14
|
+
"desktop": {
|
|
15
|
+
"enabled": true,
|
|
16
|
+
"priority": 1
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"relay": {
|
|
20
|
+
"enabled": false,
|
|
21
|
+
"port": 3000,
|
|
22
|
+
"auth": {
|
|
23
|
+
"enabled": false,
|
|
24
|
+
"token": null
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-hook-notify.d.ts","sourceRoot":"","sources":["../claude-hook-notify.ts"],"names":[],"mappings":";AAEA;;;GAGG"}
|