claude-code-telegrammer 0.4.0__tar.gz → 0.4.1__tar.gz
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.
- {claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/PKG-INFO +66 -18
- {claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/README.md +65 -17
- claude_code_telegrammer-0.4.1/bin/claude-code-telegrammer-hook +187 -0
- claude_code_telegrammer-0.4.1/bin/claude-code-telegrammer-init +104 -0
- {claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/pyproject.toml +1 -1
- {claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/ts/lib/poller.ts +7 -1
- {claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/.github/workflows/publish-pypi.yml +0 -0
- {claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/.github/workflows/test.yml +0 -0
- {claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/.gitignore +0 -0
- {claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/.readthedocs.yaml +0 -0
- {claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/LICENSE +0 -0
- {claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/docs/scitex-icon-navy-inverted.png +0 -0
- {claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/docs/scitex-logo-blue-cropped.png +0 -0
- {claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/docs/sphinx/_static/.gitkeep +0 -0
- {claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/docs/sphinx/_templates/.gitkeep +0 -0
- {claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/docs/sphinx/api/claude_code_telegrammer.rst +0 -0
- {claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/docs/sphinx/conf.py +0 -0
- {claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/docs/sphinx/index.rst +0 -0
- {claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/docs/sphinx/installation.rst +0 -0
- {claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/docs/sphinx/quickstart.rst +0 -0
- {claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/docs/sphinx/requirements.txt +0 -0
- {claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/hooks/enforce_background_subagents.sh +0 -0
- {claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/lib/auto-response.sh +0 -0
- {claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/lib/common.sh +0 -0
- {claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/lib/lock.sh +0 -0
- {claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/lib/orochi-relay.sh +0 -0
- {claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/lib/state-detection.sh +0 -0
- {claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/src/claude_code_telegrammer/__init__.py +0 -0
- {claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/src/claude_code_telegrammer/_skills/claude-code-telegrammer/SKILL.md +0 -0
- {claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/src/claude_code_telegrammer.egg-info/PKG-INFO +0 -0
- {claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/src/claude_code_telegrammer.egg-info/SOURCES.txt +0 -0
- {claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/src/claude_code_telegrammer.egg-info/dependency_links.txt +0 -0
- {claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/src/claude_code_telegrammer.egg-info/entry_points.txt +0 -0
- {claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/src/claude_code_telegrammer.egg-info/top_level.txt +0 -0
- {claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/tests/test-state-detection.sh +0 -0
- {claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/ts/.gitignore +0 -0
- {claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/ts/bun.lock +0 -0
- {claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/ts/bunfig.toml +0 -0
- {claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/ts/dist/telegram-server.js +0 -0
- {claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/ts/lib/access.ts +0 -0
- {claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/ts/lib/attachments.ts +0 -0
- {claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/ts/lib/config.ts +0 -0
- {claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/ts/lib/lock.ts +0 -0
- {claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/ts/lib/log.ts +0 -0
- {claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/ts/lib/store.ts +0 -0
- {claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/ts/lib/telegram-api.ts +0 -0
- {claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/ts/lib/tools.ts +0 -0
- {claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/ts/package.json +0 -0
- {claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/ts/telegram-server.ts +0 -0
- {claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/ts/test/access.test.ts +0 -0
- {claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/ts/test/config.test.ts +0 -0
- {claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/ts/test/lock.test.ts +0 -0
- {claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/ts/test/preload.ts +0 -0
- {claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/ts/test/store.test.ts +0 -0
- {claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/ts/test/telegram-api.test.ts +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: claude-code-telegrammer
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.1
|
|
4
4
|
Summary: Custom Telegram MCP server + TUI auto-responder for running Claude Code as an autonomous Telegram agent
|
|
5
5
|
Project-URL: Homepage, https://github.com/ywatanabe1989/claude-code-telegrammer
|
|
6
6
|
Project-URL: Repository, https://github.com/ywatanabe1989/claude-code-telegrammer.git
|
|
@@ -62,23 +62,63 @@ Description-Content-Type: text/markdown
|
|
|
62
62
|
|
|
63
63
|
---
|
|
64
64
|
|
|
65
|
-
## Problem
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
65
|
+
## Problem and Solution
|
|
66
|
+
|
|
67
|
+
<table>
|
|
68
|
+
<tr>
|
|
69
|
+
<th align="center">#</th>
|
|
70
|
+
<th>Problem</th>
|
|
71
|
+
<th>Solution</th>
|
|
72
|
+
</tr>
|
|
73
|
+
<tr valign="top">
|
|
74
|
+
<td align="center">1</td>
|
|
75
|
+
<td><h4>Hardcoded paths</h4>The official plugin hardcodes <code>~/.claude/</code> as its state directory (<a href="https://github.com/anthropics/claude-code/issues/851">#851</a>), making it impossible to run multiple bots or customize where access.json lives.</td>
|
|
76
|
+
<td><h4>Configurable state directory</h4>All state (DB, lock, access config) lives under <code>CLAUDE_CODE_TELEGRAMMER_TELEGRAM_STATE_DIR</code>. Run as many bots as you want, each with its own isolated state.</td>
|
|
77
|
+
</tr>
|
|
78
|
+
<tr valign="top">
|
|
79
|
+
<td align="center">2</td>
|
|
80
|
+
<td><h4>409 Conflict crashes</h4>No single-instance guard — multiple sessions polling the same bot get 409 errors and crash each other (<a href="https://github.com/anthropics/claude-code/issues/1075">#1075</a>).</td>
|
|
81
|
+
<td><h4>PID-based lock</h4>Automatic single-instance enforcement via PID lock file. Second instance detects the conflict and waits instead of crashing.</td>
|
|
82
|
+
</tr>
|
|
83
|
+
<tr valign="top">
|
|
84
|
+
<td align="center">3</td>
|
|
85
|
+
<td><h4>Zombie CPU consumption</h4>After session ends, the plugin process lingers at 100% CPU — requires manual kill (<a href="https://github.com/anthropics/claude-code/issues/1146">#1146</a>).</td>
|
|
86
|
+
<td><h4>Clean shutdown</h4>Exits gracefully on stdin close, SIGTERM, or SIGINT. No zombies, no manual cleanup.</td>
|
|
87
|
+
</tr>
|
|
88
|
+
<tr valign="top">
|
|
89
|
+
<td align="center">4</td>
|
|
90
|
+
<td><h4>Only 3 basic tools</h4>The official plugin provides just send, get_updates, and set_reaction — no history, no search, no file handling, no message editing.</td>
|
|
91
|
+
<td><h4>10 MCP tools</h4>reply, react, edit_message, get_history, get_unread, mark_read, download_attachment, send_document, search_messages, get_context — everything an autonomous agent needs.</td>
|
|
92
|
+
</tr>
|
|
93
|
+
<tr valign="top">
|
|
94
|
+
<td align="center">5</td>
|
|
95
|
+
<td><h4>No message persistence</h4>Messages vanish after delivery. No way to search past conversations, track read status, or build context from history.</td>
|
|
96
|
+
<td><h4>SQLite message store</h4>All messages persisted in WAL-mode SQLite with full-text search, reply threading (reply_to_message_id), read/replied tracking, and attachment metadata.</td>
|
|
97
|
+
</tr>
|
|
98
|
+
<tr valign="top">
|
|
99
|
+
<td align="center">6</td>
|
|
100
|
+
<td><h4>No access control for groups</h4>Basic allowlist only — no per-group policies, no hot-reload when config changes.</td>
|
|
101
|
+
<td><h4>DM + group policies</h4>Allowlist-based access control with separate DM and group chat policies via <code>access.json</code>, hot-reloaded on file change (mtime-based).</td>
|
|
102
|
+
</tr>
|
|
103
|
+
<tr valign="top">
|
|
104
|
+
<td align="center">7</td>
|
|
105
|
+
<td><h4>No attachment support</h4>Cannot download inbound files or upload documents to chats.</td>
|
|
106
|
+
<td><h4>Full attachment handling</h4>Inbound photos, documents, voice, audio, and video are auto-downloaded. Upload local files via <code>send_document</code> tool.</td>
|
|
107
|
+
</tr>
|
|
108
|
+
<tr valign="top">
|
|
109
|
+
<td align="center">8</td>
|
|
110
|
+
<td><h4>Sessions stall unattended</h4>Claude Code halts at permission prompts or idle states with no way to recover — the agent just stops working.</td>
|
|
111
|
+
<td><h4>TUI Watchdog</h4>Polls GNU Screen buffer, detects TUI state via pattern matching, sends keystrokes to auto-accept prompts and re-engage on idle. Throttled with burst limits.</td>
|
|
112
|
+
</tr>
|
|
113
|
+
</table>
|
|
114
|
+
|
|
115
|
+
<p align="center"><sub><b>Table 1.</b> Eight issues with the official Telegram plugin (as of April 2026) and how claude-code-telegrammer addresses each. These problems make the official plugin unusable for production autonomous agents.</sub></p>
|
|
116
|
+
|
|
117
|
+
### Architecture
|
|
118
|
+
|
|
119
|
+
1. **Custom Telegram MCP Server** (`ts/`) -- Self-contained Bun + MCP server. 10 tools, SQLite persistence, allowlist access control, attachment handling, reaction support. Incoming messages acknowledged with 📩. Built-in responsiveness policy directs the agent to reply immediately and delegate heavy work to background subagents.
|
|
120
|
+
|
|
121
|
+
2. **TUI Watchdog** (`lib/`) -- Polls a GNU Screen session, detects Claude Code's TUI state via pattern matching, and sends keystrokes to keep the agent running unattended (auto-accepts permission prompts, re-engages on idle). Throttled with burst limits to prevent runaway responses. Orchestration handled by [scitex-agent-container](https://github.com/ywatanabe1989/scitex-agent-container).
|
|
82
122
|
|
|
83
123
|
<details>
|
|
84
124
|
<summary><strong>MCP Tools (10)</strong></summary>
|
|
@@ -98,6 +138,14 @@ Two subsystems that replace and extend the official plugin:
|
|
|
98
138
|
|
|
99
139
|
</details>
|
|
100
140
|
|
|
141
|
+
### Important: Bot Token Exclusivity
|
|
142
|
+
|
|
143
|
+
This MCP server **must be the sole consumer** of its configured Telegram bot token. The Telegram Bot API enforces a single active `getUpdates` long-polling connection per token. If another component (e.g., `scitex-orochi`'s Telegram bridge) also polls the same token, **HTTP 409 Conflict** errors will occur, causing both consumers to miss messages.
|
|
144
|
+
|
|
145
|
+
**If you see 409 errors:**
|
|
146
|
+
- Disable the other polling component, **or**
|
|
147
|
+
- Use a separate bot token for each component
|
|
148
|
+
|
|
101
149
|
## Installation
|
|
102
150
|
|
|
103
151
|
### Prerequisites
|
|
@@ -29,23 +29,63 @@
|
|
|
29
29
|
|
|
30
30
|
---
|
|
31
31
|
|
|
32
|
-
## Problem
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
32
|
+
## Problem and Solution
|
|
33
|
+
|
|
34
|
+
<table>
|
|
35
|
+
<tr>
|
|
36
|
+
<th align="center">#</th>
|
|
37
|
+
<th>Problem</th>
|
|
38
|
+
<th>Solution</th>
|
|
39
|
+
</tr>
|
|
40
|
+
<tr valign="top">
|
|
41
|
+
<td align="center">1</td>
|
|
42
|
+
<td><h4>Hardcoded paths</h4>The official plugin hardcodes <code>~/.claude/</code> as its state directory (<a href="https://github.com/anthropics/claude-code/issues/851">#851</a>), making it impossible to run multiple bots or customize where access.json lives.</td>
|
|
43
|
+
<td><h4>Configurable state directory</h4>All state (DB, lock, access config) lives under <code>CLAUDE_CODE_TELEGRAMMER_TELEGRAM_STATE_DIR</code>. Run as many bots as you want, each with its own isolated state.</td>
|
|
44
|
+
</tr>
|
|
45
|
+
<tr valign="top">
|
|
46
|
+
<td align="center">2</td>
|
|
47
|
+
<td><h4>409 Conflict crashes</h4>No single-instance guard — multiple sessions polling the same bot get 409 errors and crash each other (<a href="https://github.com/anthropics/claude-code/issues/1075">#1075</a>).</td>
|
|
48
|
+
<td><h4>PID-based lock</h4>Automatic single-instance enforcement via PID lock file. Second instance detects the conflict and waits instead of crashing.</td>
|
|
49
|
+
</tr>
|
|
50
|
+
<tr valign="top">
|
|
51
|
+
<td align="center">3</td>
|
|
52
|
+
<td><h4>Zombie CPU consumption</h4>After session ends, the plugin process lingers at 100% CPU — requires manual kill (<a href="https://github.com/anthropics/claude-code/issues/1146">#1146</a>).</td>
|
|
53
|
+
<td><h4>Clean shutdown</h4>Exits gracefully on stdin close, SIGTERM, or SIGINT. No zombies, no manual cleanup.</td>
|
|
54
|
+
</tr>
|
|
55
|
+
<tr valign="top">
|
|
56
|
+
<td align="center">4</td>
|
|
57
|
+
<td><h4>Only 3 basic tools</h4>The official plugin provides just send, get_updates, and set_reaction — no history, no search, no file handling, no message editing.</td>
|
|
58
|
+
<td><h4>10 MCP tools</h4>reply, react, edit_message, get_history, get_unread, mark_read, download_attachment, send_document, search_messages, get_context — everything an autonomous agent needs.</td>
|
|
59
|
+
</tr>
|
|
60
|
+
<tr valign="top">
|
|
61
|
+
<td align="center">5</td>
|
|
62
|
+
<td><h4>No message persistence</h4>Messages vanish after delivery. No way to search past conversations, track read status, or build context from history.</td>
|
|
63
|
+
<td><h4>SQLite message store</h4>All messages persisted in WAL-mode SQLite with full-text search, reply threading (reply_to_message_id), read/replied tracking, and attachment metadata.</td>
|
|
64
|
+
</tr>
|
|
65
|
+
<tr valign="top">
|
|
66
|
+
<td align="center">6</td>
|
|
67
|
+
<td><h4>No access control for groups</h4>Basic allowlist only — no per-group policies, no hot-reload when config changes.</td>
|
|
68
|
+
<td><h4>DM + group policies</h4>Allowlist-based access control with separate DM and group chat policies via <code>access.json</code>, hot-reloaded on file change (mtime-based).</td>
|
|
69
|
+
</tr>
|
|
70
|
+
<tr valign="top">
|
|
71
|
+
<td align="center">7</td>
|
|
72
|
+
<td><h4>No attachment support</h4>Cannot download inbound files or upload documents to chats.</td>
|
|
73
|
+
<td><h4>Full attachment handling</h4>Inbound photos, documents, voice, audio, and video are auto-downloaded. Upload local files via <code>send_document</code> tool.</td>
|
|
74
|
+
</tr>
|
|
75
|
+
<tr valign="top">
|
|
76
|
+
<td align="center">8</td>
|
|
77
|
+
<td><h4>Sessions stall unattended</h4>Claude Code halts at permission prompts or idle states with no way to recover — the agent just stops working.</td>
|
|
78
|
+
<td><h4>TUI Watchdog</h4>Polls GNU Screen buffer, detects TUI state via pattern matching, sends keystrokes to auto-accept prompts and re-engage on idle. Throttled with burst limits.</td>
|
|
79
|
+
</tr>
|
|
80
|
+
</table>
|
|
81
|
+
|
|
82
|
+
<p align="center"><sub><b>Table 1.</b> Eight issues with the official Telegram plugin (as of April 2026) and how claude-code-telegrammer addresses each. These problems make the official plugin unusable for production autonomous agents.</sub></p>
|
|
83
|
+
|
|
84
|
+
### Architecture
|
|
85
|
+
|
|
86
|
+
1. **Custom Telegram MCP Server** (`ts/`) -- Self-contained Bun + MCP server. 10 tools, SQLite persistence, allowlist access control, attachment handling, reaction support. Incoming messages acknowledged with 📩. Built-in responsiveness policy directs the agent to reply immediately and delegate heavy work to background subagents.
|
|
87
|
+
|
|
88
|
+
2. **TUI Watchdog** (`lib/`) -- Polls a GNU Screen session, detects Claude Code's TUI state via pattern matching, and sends keystrokes to keep the agent running unattended (auto-accepts permission prompts, re-engages on idle). Throttled with burst limits to prevent runaway responses. Orchestration handled by [scitex-agent-container](https://github.com/ywatanabe1989/scitex-agent-container).
|
|
49
89
|
|
|
50
90
|
<details>
|
|
51
91
|
<summary><strong>MCP Tools (10)</strong></summary>
|
|
@@ -65,6 +105,14 @@ Two subsystems that replace and extend the official plugin:
|
|
|
65
105
|
|
|
66
106
|
</details>
|
|
67
107
|
|
|
108
|
+
### Important: Bot Token Exclusivity
|
|
109
|
+
|
|
110
|
+
This MCP server **must be the sole consumer** of its configured Telegram bot token. The Telegram Bot API enforces a single active `getUpdates` long-polling connection per token. If another component (e.g., `scitex-orochi`'s Telegram bridge) also polls the same token, **HTTP 409 Conflict** errors will occur, causing both consumers to miss messages.
|
|
111
|
+
|
|
112
|
+
**If you see 409 errors:**
|
|
113
|
+
- Disable the other polling component, **or**
|
|
114
|
+
- Use a separate bot token for each component
|
|
115
|
+
|
|
68
116
|
## Installation
|
|
69
117
|
|
|
70
118
|
### Prerequisites
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# claude-code-telegrammer-hook — Entry point for scitex-agent-container hooks
|
|
3
|
+
# Called by agent-container's post_start/pre_stop hooks.
|
|
4
|
+
#
|
|
5
|
+
# Usage:
|
|
6
|
+
# claude-code-telegrammer-hook pre-start # Write access.json (before Claude Code)
|
|
7
|
+
# claude-code-telegrammer-hook post-start # Start watchdog + send startup commands
|
|
8
|
+
# claude-code-telegrammer-hook pre-stop # Stop watchdog
|
|
9
|
+
|
|
10
|
+
set -euo pipefail
|
|
11
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
12
|
+
source "$SCRIPT_DIR/lib/common.sh"
|
|
13
|
+
|
|
14
|
+
# Expect these env vars from agent-container:
|
|
15
|
+
# AGENT_CONFIG_PATH — full path to agent YAML
|
|
16
|
+
# AGENT_SCREEN_NAME — screen session name
|
|
17
|
+
# AGENT_NAME — agent name
|
|
18
|
+
|
|
19
|
+
cmd="${1:-}"
|
|
20
|
+
|
|
21
|
+
case "$cmd" in
|
|
22
|
+
pre-start)
|
|
23
|
+
if [[ -z "${AGENT_CONFIG_PATH:-}" ]]; then
|
|
24
|
+
log_warn "AGENT_CONFIG_PATH not set, skipping pre-start"
|
|
25
|
+
exit 0
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
# 1. Map bot_token_env → CLAUDE_CODE_TELEGRAMMER_TELEGRAM_BOT_TOKEN
|
|
29
|
+
BOT_TOKEN_ENV=$(yaml_get "$AGENT_CONFIG_PATH" "spec.telegram.bot_token_env" "")
|
|
30
|
+
if [[ -n "$BOT_TOKEN_ENV" && "$BOT_TOKEN_ENV" != "CLAUDE_CODE_TELEGRAMMER_TELEGRAM_BOT_TOKEN" ]]; then
|
|
31
|
+
TOKEN_VAL="${!BOT_TOKEN_ENV:-}"
|
|
32
|
+
if [[ -n "$TOKEN_VAL" ]]; then
|
|
33
|
+
export CLAUDE_CODE_TELEGRAMMER_TELEGRAM_BOT_TOKEN="$TOKEN_VAL"
|
|
34
|
+
fi
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
# 2. Write access.json
|
|
38
|
+
AUTO_CONNECT=$(yaml_get "$AGENT_CONFIG_PATH" "spec.telegram.auto_connect" "true")
|
|
39
|
+
STATE_DIR="${CLAUDE_CODE_TELEGRAMMER_TELEGRAM_STATE_DIR:-$HOME/.scitex/agent-container/telegram}"
|
|
40
|
+
|
|
41
|
+
# Resolve bot_id from token for state dir
|
|
42
|
+
if [[ -n "${CLAUDE_CODE_TELEGRAMMER_TELEGRAM_BOT_TOKEN:-}" ]]; then
|
|
43
|
+
BOT_ID="${CLAUDE_CODE_TELEGRAMMER_TELEGRAM_BOT_TOKEN%%:*}"
|
|
44
|
+
STATE_DIR="$HOME/.scitex/agent-container/telegram/$BOT_ID"
|
|
45
|
+
fi
|
|
46
|
+
mkdir -p "$STATE_DIR"
|
|
47
|
+
|
|
48
|
+
# Parse allowed_users via Python (yaml_get returns flat values, not JSON)
|
|
49
|
+
if [[ "$AUTO_CONNECT" == "true" || "$AUTO_CONNECT" == "True" ]]; then
|
|
50
|
+
python3 -c "
|
|
51
|
+
import yaml, json
|
|
52
|
+
with open('$AGENT_CONFIG_PATH') as f:
|
|
53
|
+
raw = yaml.safe_load(f)
|
|
54
|
+
users = raw.get('spec',{}).get('telegram',{}).get('allowed_users',[]) or []
|
|
55
|
+
users = [str(u) for u in users]
|
|
56
|
+
data = {'dmPolicy': 'allowlist', 'allowFrom': users}
|
|
57
|
+
with open('$STATE_DIR/access.json', 'w') as f:
|
|
58
|
+
json.dump(data, f, indent=2)
|
|
59
|
+
f.write('\n')
|
|
60
|
+
print('Wrote access.json with', len(users), 'allowed users to $STATE_DIR')
|
|
61
|
+
"
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
# 3. Write .env for the MCP server
|
|
65
|
+
echo "CLAUDE_CODE_TELEGRAMMER_TELEGRAM_BOT_TOKEN=${CLAUDE_CODE_TELEGRAMMER_TELEGRAM_BOT_TOKEN:-}" > "$STATE_DIR/.env"
|
|
66
|
+
chmod 600 "$STATE_DIR/.env"
|
|
67
|
+
log_info "Wrote .env to $STATE_DIR"
|
|
68
|
+
|
|
69
|
+
# 4. Write MCP config JSON for --mcp-config
|
|
70
|
+
AGENT_NAME="${AGENT_NAME:-telegrammer}"
|
|
71
|
+
MCP_DIR="$HOME/.scitex/agent-container/cache/mcp-configs"
|
|
72
|
+
mkdir -p "$MCP_DIR"
|
|
73
|
+
# Find telegram-server.ts: check project root first, then relative to SCRIPT_DIR
|
|
74
|
+
TS_SERVER=""
|
|
75
|
+
for candidate in \
|
|
76
|
+
"$SCRIPT_DIR/../ts/telegram-server.ts" \
|
|
77
|
+
"$SCRIPT_DIR/ts/telegram-server.ts" \
|
|
78
|
+
"$(python3 -c 'import claude_code_telegrammer; import os; print(os.path.join(os.path.dirname(claude_code_telegrammer.__file__), "..", "..", "ts", "telegram-server.ts"))' 2>/dev/null)" \
|
|
79
|
+
; do
|
|
80
|
+
if [[ -f "$candidate" ]]; then
|
|
81
|
+
TS_SERVER="$(cd "$(dirname "$candidate")" && pwd)/$(basename "$candidate")"
|
|
82
|
+
break
|
|
83
|
+
fi
|
|
84
|
+
done
|
|
85
|
+
if [[ -z "$TS_SERVER" ]]; then
|
|
86
|
+
log_warn "telegram-server.ts not found"
|
|
87
|
+
exit 1
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
python3 -c "
|
|
91
|
+
import yaml, json, os
|
|
92
|
+
with open('$AGENT_CONFIG_PATH') as f:
|
|
93
|
+
raw = yaml.safe_load(f)
|
|
94
|
+
users = raw.get('spec',{}).get('telegram',{}).get('allowed_users',[]) or []
|
|
95
|
+
users_str = ','.join(str(u) for u in users)
|
|
96
|
+
config = {
|
|
97
|
+
'mcpServers': {
|
|
98
|
+
'claude-code-telegrammer': {
|
|
99
|
+
'type': 'stdio',
|
|
100
|
+
'command': 'bun',
|
|
101
|
+
'args': ['run', '$TS_SERVER'],
|
|
102
|
+
'env': {
|
|
103
|
+
'CLAUDE_CODE_TELEGRAMMER_TELEGRAM_BOT_TOKEN': os.environ.get('CLAUDE_CODE_TELEGRAMMER_TELEGRAM_BOT_TOKEN', ''),
|
|
104
|
+
'CLAUDE_CODE_TELEGRAMMER_TELEGRAM_STATE_DIR': '$STATE_DIR',
|
|
105
|
+
'CLAUDE_CODE_TELEGRAMMER_TELEGRAM_ALLOWED_USERS': users_str
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
# Write to both /tmp (for --mcp-config) and workdir .mcp.json (for --dangerously-load-development-channels)
|
|
111
|
+
path = '$MCP_DIR/mcp-$AGENT_NAME.json'
|
|
112
|
+
with open(path, 'w') as f:
|
|
113
|
+
json.dump(config, f, indent=2)
|
|
114
|
+
f.write('\n')
|
|
115
|
+
print('Wrote MCP config to', path)
|
|
116
|
+
|
|
117
|
+
# Also write to workdir .mcp.json so --dangerously-load-development-channels can find it
|
|
118
|
+
import yaml as _yaml
|
|
119
|
+
with open('$AGENT_CONFIG_PATH') as f:
|
|
120
|
+
agent_cfg = _yaml.safe_load(f)
|
|
121
|
+
workdir = os.path.expanduser(agent_cfg.get('spec', {}).get('workdir', '~/proj'))
|
|
122
|
+
mcp_json_path = os.path.join(workdir, '.mcp.json')
|
|
123
|
+
# Merge with existing .mcp.json if present
|
|
124
|
+
existing = {}
|
|
125
|
+
try:
|
|
126
|
+
with open(mcp_json_path) as f:
|
|
127
|
+
existing = json.load(f)
|
|
128
|
+
except (FileNotFoundError, json.JSONDecodeError):
|
|
129
|
+
pass
|
|
130
|
+
existing.setdefault('mcpServers', {})
|
|
131
|
+
existing['mcpServers']['claude-code-telegrammer'] = config['mcpServers']['claude-code-telegrammer']
|
|
132
|
+
with open(mcp_json_path, 'w') as f:
|
|
133
|
+
json.dump(existing, f, indent=2)
|
|
134
|
+
f.write('\n')
|
|
135
|
+
print('Wrote .mcp.json to', mcp_json_path)
|
|
136
|
+
"
|
|
137
|
+
;;
|
|
138
|
+
|
|
139
|
+
post-start)
|
|
140
|
+
# Send startup commands (screen must exist)
|
|
141
|
+
SESSION="${AGENT_SCREEN_NAME:-cld-telegram}"
|
|
142
|
+
if [[ -n "${AGENT_CONFIG_PATH:-}" ]]; then
|
|
143
|
+
claude-code-telegrammer-init --session "$SESSION" --config "$AGENT_CONFIG_PATH"
|
|
144
|
+
fi
|
|
145
|
+
|
|
146
|
+
# Start watchdog if enabled
|
|
147
|
+
if [[ -n "${AGENT_CONFIG_PATH:-}" ]]; then
|
|
148
|
+
WATCHDOG_ENABLED=$(yaml_get "$AGENT_CONFIG_PATH" "spec.watchdog.enabled" "false")
|
|
149
|
+
if [[ "$WATCHDOG_ENABLED" == "true" || "$WATCHDOG_ENABLED" == "True" ]]; then
|
|
150
|
+
INTERVAL=$(yaml_get "$AGENT_CONFIG_PATH" "spec.watchdog.interval" "1.5")
|
|
151
|
+
RESP_YN=$(yaml_get "$AGENT_CONFIG_PATH" "spec.watchdog.responses.y_n" "1")
|
|
152
|
+
RESP_YYN=$(yaml_get "$AGENT_CONFIG_PATH" "spec.watchdog.responses.y_y_n" "2")
|
|
153
|
+
RESP_WAIT=$(yaml_get "$AGENT_CONFIG_PATH" "spec.watchdog.responses.waiting" "/speak-and-call")
|
|
154
|
+
|
|
155
|
+
WATCHDOG_SESSION="${SESSION}-watchdog"
|
|
156
|
+
|
|
157
|
+
export CLAUDE_CODE_TELEGRAMMER_SESSION="$SESSION"
|
|
158
|
+
export CLAUDE_CODE_TELEGRAMMER_WATCHDOG_INTERVAL="$INTERVAL"
|
|
159
|
+
export CLAUDE_CODE_TELEGRAMMER_RESP_Y_N="$RESP_YN"
|
|
160
|
+
export CLAUDE_CODE_TELEGRAMMER_RESP_Y_Y_N="$RESP_YYN"
|
|
161
|
+
export CLAUDE_CODE_TELEGRAMMER_RESP_WAITING="$RESP_WAIT"
|
|
162
|
+
|
|
163
|
+
WATCHDOG_BIN=$(which claude-code-telegrammer-watchdog 2>/dev/null || echo "")
|
|
164
|
+
if [[ -n "$WATCHDOG_BIN" ]]; then
|
|
165
|
+
screen -dmS "$WATCHDOG_SESSION" bash -c "$WATCHDOG_BIN --session $SESSION --interval $INTERVAL"
|
|
166
|
+
log_info "Watchdog started: $WATCHDOG_SESSION"
|
|
167
|
+
else
|
|
168
|
+
log_warn "claude-code-telegrammer-watchdog not found in PATH"
|
|
169
|
+
fi
|
|
170
|
+
fi
|
|
171
|
+
fi
|
|
172
|
+
;;
|
|
173
|
+
|
|
174
|
+
pre-stop)
|
|
175
|
+
SESSION="${AGENT_SCREEN_NAME:-cld-telegram}"
|
|
176
|
+
WATCHDOG_SESSION="${SESSION}-watchdog"
|
|
177
|
+
if screen -ls 2>/dev/null | grep -q "$WATCHDOG_SESSION"; then
|
|
178
|
+
screen -S "$WATCHDOG_SESSION" -X quit 2>/dev/null || true
|
|
179
|
+
log_info "Watchdog stopped: $WATCHDOG_SESSION"
|
|
180
|
+
fi
|
|
181
|
+
;;
|
|
182
|
+
|
|
183
|
+
*)
|
|
184
|
+
echo "Usage: claude-code-telegrammer-hook {post-start|pre-stop}"
|
|
185
|
+
exit 1
|
|
186
|
+
;;
|
|
187
|
+
esac
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# claude-code-telegrammer-init — Send startup commands to a Claude Code screen session
|
|
3
|
+
# and optionally configure Telegram access.json.
|
|
4
|
+
#
|
|
5
|
+
# Usage: claude-code-telegrammer-init --session cld-telegram --config telegram-master.yaml
|
|
6
|
+
#
|
|
7
|
+
# Requires: yq (or python3 with PyYAML as fallback)
|
|
8
|
+
|
|
9
|
+
set -euo pipefail
|
|
10
|
+
|
|
11
|
+
SESSION=""
|
|
12
|
+
CONFIG=""
|
|
13
|
+
|
|
14
|
+
usage() {
|
|
15
|
+
echo "Usage: claude-code-telegrammer-init --session <screen-session> --config <yaml-config>"
|
|
16
|
+
echo ""
|
|
17
|
+
echo "Options:"
|
|
18
|
+
echo " --session Screen session name to send commands to"
|
|
19
|
+
echo " --config YAML config file with startup_commands and telegram sections"
|
|
20
|
+
echo " --help Show this help message"
|
|
21
|
+
exit "${1:-0}"
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
while [[ $# -gt 0 ]]; do
|
|
25
|
+
case "$1" in
|
|
26
|
+
--session) SESSION="$2"; shift 2 ;;
|
|
27
|
+
--config) CONFIG="$2"; shift 2 ;;
|
|
28
|
+
--help) usage 0 ;;
|
|
29
|
+
*) echo "Unknown option: $1"; usage 1 ;;
|
|
30
|
+
esac
|
|
31
|
+
done
|
|
32
|
+
|
|
33
|
+
if [[ -z "$SESSION" || -z "$CONFIG" ]]; then
|
|
34
|
+
echo "Error: --session and --config are required."
|
|
35
|
+
usage 1
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
if [[ ! -f "$CONFIG" ]]; then
|
|
39
|
+
echo "Error: Config file not found: $CONFIG"
|
|
40
|
+
exit 1
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
# Use python3 to parse YAML (always available in our environment)
|
|
44
|
+
parse_yaml() {
|
|
45
|
+
python3 -c "
|
|
46
|
+
import sys, yaml, json
|
|
47
|
+
|
|
48
|
+
with open('$CONFIG') as f:
|
|
49
|
+
raw = yaml.safe_load(f)
|
|
50
|
+
|
|
51
|
+
spec = raw.get('spec', {})
|
|
52
|
+
result = {
|
|
53
|
+
'startup_commands': spec.get('startup_commands', []) or [],
|
|
54
|
+
'telegram': spec.get('telegram', {}) or {},
|
|
55
|
+
}
|
|
56
|
+
print(json.dumps(result))
|
|
57
|
+
"
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
PARSED=$(parse_yaml)
|
|
61
|
+
|
|
62
|
+
# Setup Telegram access.json if configured
|
|
63
|
+
AUTO_CONNECT=$(echo "$PARSED" | python3 -c "import sys,json; d=json.load(sys.stdin); print(str(d.get('telegram',{}).get('auto_connect',True)).lower())")
|
|
64
|
+
ALLOWED_USERS=$(echo "$PARSED" | python3 -c "import sys,json; d=json.load(sys.stdin); users=d.get('telegram',{}).get('allowed_users',[]); print(json.dumps(users))")
|
|
65
|
+
|
|
66
|
+
if [[ "$AUTO_CONNECT" == "true" && "$ALLOWED_USERS" != "[]" ]]; then
|
|
67
|
+
ACCESS_DIR="${CLAUDE_CODE_TELEGRAMMER_TELEGRAM_STATE_DIR:-$HOME/.scitex/agent-container/telegram}"
|
|
68
|
+
mkdir -p "$ACCESS_DIR"
|
|
69
|
+
python3 -c "
|
|
70
|
+
import json
|
|
71
|
+
users = json.loads('$ALLOWED_USERS')
|
|
72
|
+
data = {'dmPolicy': 'allowlist', 'allowFrom': [str(u) for u in users]}
|
|
73
|
+
with open('$ACCESS_DIR/access.json', 'w') as f:
|
|
74
|
+
json.dump(data, f, indent=2)
|
|
75
|
+
f.write('\n')
|
|
76
|
+
print('Wrote access.json with', len(users), 'allowed users')
|
|
77
|
+
"
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
# Send startup commands with delays
|
|
81
|
+
NUM_COMMANDS=$(echo "$PARSED" | python3 -c "import sys,json; d=json.load(sys.stdin); print(len(d.get('startup_commands',[])))")
|
|
82
|
+
|
|
83
|
+
if [[ "$NUM_COMMANDS" -gt 0 ]]; then
|
|
84
|
+
echo "Sending $NUM_COMMANDS startup command(s) to session: $SESSION"
|
|
85
|
+
|
|
86
|
+
echo "$PARSED" | python3 -c "
|
|
87
|
+
import sys, json
|
|
88
|
+
d = json.load(sys.stdin)
|
|
89
|
+
for cmd in d.get('startup_commands', []):
|
|
90
|
+
delay = cmd.get('delay', 0)
|
|
91
|
+
command = cmd.get('command', '')
|
|
92
|
+
if command:
|
|
93
|
+
print(f'{delay}\t{command}')
|
|
94
|
+
" | while IFS=$'\t' read -r delay command; do
|
|
95
|
+
if [[ "$delay" -gt 0 ]]; then
|
|
96
|
+
echo " Waiting ${delay}s before sending: $command"
|
|
97
|
+
sleep "$delay"
|
|
98
|
+
fi
|
|
99
|
+
screen -S "$SESSION" -X stuff "${command}\r"
|
|
100
|
+
echo " Sent: $command"
|
|
101
|
+
done
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
echo "claude-code-telegrammer-init complete."
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "claude-code-telegrammer"
|
|
7
|
-
version = "0.4.
|
|
7
|
+
version = "0.4.1"
|
|
8
8
|
description = "Custom Telegram MCP server + TUI auto-responder for running Claude Code as an autonomous Telegram agent"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "AGPL-3.0"
|
|
@@ -85,7 +85,13 @@ export async function startPolling(mcp: Server): Promise<void> {
|
|
|
85
85
|
} catch (err) {
|
|
86
86
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
87
87
|
if (errMsg.includes("409")) {
|
|
88
|
-
log(
|
|
88
|
+
log(
|
|
89
|
+
"poller",
|
|
90
|
+
"409 Conflict — another process is polling this bot token. " +
|
|
91
|
+
"Only one getUpdates consumer is allowed per token. " +
|
|
92
|
+
"If scitex-orochi's Telegram bridge is active, disable it or use a separate bot token. " +
|
|
93
|
+
"Retrying in 5s...",
|
|
94
|
+
);
|
|
89
95
|
await new Promise((r) => setTimeout(r, 5000));
|
|
90
96
|
} else {
|
|
91
97
|
log("poller", `getUpdates error: ${errMsg}. Retrying in 3s...`);
|
{claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/.github/workflows/publish-pypi.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/docs/scitex-icon-navy-inverted.png
RENAMED
|
File without changes
|
{claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/docs/scitex-logo-blue-cropped.png
RENAMED
|
File without changes
|
{claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/docs/sphinx/_static/.gitkeep
RENAMED
|
File without changes
|
{claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/docs/sphinx/_templates/.gitkeep
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/docs/sphinx/installation.rst
RENAMED
|
File without changes
|
|
File without changes
|
{claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/docs/sphinx/requirements.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/tests/test-state-detection.sh
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{claude_code_telegrammer-0.4.0 → claude_code_telegrammer-0.4.1}/ts/test/telegram-api.test.ts
RENAMED
|
File without changes
|