claude-code-telegrammer 0.4.1__tar.gz → 0.4.2__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.
Files changed (58) hide show
  1. claude_code_telegrammer-0.4.2/.env.example +20 -0
  2. claude_code_telegrammer-0.4.2/.mcp.json.example +14 -0
  3. {claude_code_telegrammer-0.4.1 → claude_code_telegrammer-0.4.2}/PKG-INFO +20 -18
  4. {claude_code_telegrammer-0.4.1 → claude_code_telegrammer-0.4.2}/README.md +19 -17
  5. {claude_code_telegrammer-0.4.1 → claude_code_telegrammer-0.4.2}/pyproject.toml +1 -1
  6. claude_code_telegrammer-0.4.2/src/claude_code_telegrammer/__init__.py +7 -0
  7. {claude_code_telegrammer-0.4.1 → claude_code_telegrammer-0.4.2}/ts/lib/poller.ts +49 -7
  8. claude_code_telegrammer-0.4.1/src/claude_code_telegrammer/__init__.py +0 -35
  9. {claude_code_telegrammer-0.4.1 → claude_code_telegrammer-0.4.2}/.github/workflows/publish-pypi.yml +0 -0
  10. {claude_code_telegrammer-0.4.1 → claude_code_telegrammer-0.4.2}/.github/workflows/test.yml +0 -0
  11. {claude_code_telegrammer-0.4.1 → claude_code_telegrammer-0.4.2}/.gitignore +0 -0
  12. {claude_code_telegrammer-0.4.1 → claude_code_telegrammer-0.4.2}/.readthedocs.yaml +0 -0
  13. {claude_code_telegrammer-0.4.1 → claude_code_telegrammer-0.4.2}/LICENSE +0 -0
  14. {claude_code_telegrammer-0.4.1 → claude_code_telegrammer-0.4.2}/bin/claude-code-telegrammer-hook +0 -0
  15. {claude_code_telegrammer-0.4.1 → claude_code_telegrammer-0.4.2}/bin/claude-code-telegrammer-init +0 -0
  16. {claude_code_telegrammer-0.4.1 → claude_code_telegrammer-0.4.2}/docs/scitex-icon-navy-inverted.png +0 -0
  17. {claude_code_telegrammer-0.4.1 → claude_code_telegrammer-0.4.2}/docs/scitex-logo-blue-cropped.png +0 -0
  18. {claude_code_telegrammer-0.4.1 → claude_code_telegrammer-0.4.2}/docs/sphinx/_static/.gitkeep +0 -0
  19. {claude_code_telegrammer-0.4.1 → claude_code_telegrammer-0.4.2}/docs/sphinx/_templates/.gitkeep +0 -0
  20. {claude_code_telegrammer-0.4.1 → claude_code_telegrammer-0.4.2}/docs/sphinx/api/claude_code_telegrammer.rst +0 -0
  21. {claude_code_telegrammer-0.4.1 → claude_code_telegrammer-0.4.2}/docs/sphinx/conf.py +0 -0
  22. {claude_code_telegrammer-0.4.1 → claude_code_telegrammer-0.4.2}/docs/sphinx/index.rst +0 -0
  23. {claude_code_telegrammer-0.4.1 → claude_code_telegrammer-0.4.2}/docs/sphinx/installation.rst +0 -0
  24. {claude_code_telegrammer-0.4.1 → claude_code_telegrammer-0.4.2}/docs/sphinx/quickstart.rst +0 -0
  25. {claude_code_telegrammer-0.4.1 → claude_code_telegrammer-0.4.2}/docs/sphinx/requirements.txt +0 -0
  26. {claude_code_telegrammer-0.4.1 → claude_code_telegrammer-0.4.2}/hooks/enforce_background_subagents.sh +0 -0
  27. {claude_code_telegrammer-0.4.1 → claude_code_telegrammer-0.4.2}/lib/auto-response.sh +0 -0
  28. {claude_code_telegrammer-0.4.1 → claude_code_telegrammer-0.4.2}/lib/common.sh +0 -0
  29. {claude_code_telegrammer-0.4.1 → claude_code_telegrammer-0.4.2}/lib/lock.sh +0 -0
  30. {claude_code_telegrammer-0.4.1 → claude_code_telegrammer-0.4.2}/lib/orochi-relay.sh +0 -0
  31. {claude_code_telegrammer-0.4.1 → claude_code_telegrammer-0.4.2}/lib/state-detection.sh +0 -0
  32. {claude_code_telegrammer-0.4.1 → claude_code_telegrammer-0.4.2}/src/claude_code_telegrammer/_skills/claude-code-telegrammer/SKILL.md +0 -0
  33. {claude_code_telegrammer-0.4.1 → claude_code_telegrammer-0.4.2}/src/claude_code_telegrammer.egg-info/PKG-INFO +0 -0
  34. {claude_code_telegrammer-0.4.1 → claude_code_telegrammer-0.4.2}/src/claude_code_telegrammer.egg-info/SOURCES.txt +0 -0
  35. {claude_code_telegrammer-0.4.1 → claude_code_telegrammer-0.4.2}/src/claude_code_telegrammer.egg-info/dependency_links.txt +0 -0
  36. {claude_code_telegrammer-0.4.1 → claude_code_telegrammer-0.4.2}/src/claude_code_telegrammer.egg-info/entry_points.txt +0 -0
  37. {claude_code_telegrammer-0.4.1 → claude_code_telegrammer-0.4.2}/src/claude_code_telegrammer.egg-info/top_level.txt +0 -0
  38. {claude_code_telegrammer-0.4.1 → claude_code_telegrammer-0.4.2}/tests/test-state-detection.sh +0 -0
  39. {claude_code_telegrammer-0.4.1 → claude_code_telegrammer-0.4.2}/ts/.gitignore +0 -0
  40. {claude_code_telegrammer-0.4.1 → claude_code_telegrammer-0.4.2}/ts/bun.lock +0 -0
  41. {claude_code_telegrammer-0.4.1 → claude_code_telegrammer-0.4.2}/ts/bunfig.toml +0 -0
  42. {claude_code_telegrammer-0.4.1 → claude_code_telegrammer-0.4.2}/ts/dist/telegram-server.js +0 -0
  43. {claude_code_telegrammer-0.4.1 → claude_code_telegrammer-0.4.2}/ts/lib/access.ts +0 -0
  44. {claude_code_telegrammer-0.4.1 → claude_code_telegrammer-0.4.2}/ts/lib/attachments.ts +0 -0
  45. {claude_code_telegrammer-0.4.1 → claude_code_telegrammer-0.4.2}/ts/lib/config.ts +0 -0
  46. {claude_code_telegrammer-0.4.1 → claude_code_telegrammer-0.4.2}/ts/lib/lock.ts +0 -0
  47. {claude_code_telegrammer-0.4.1 → claude_code_telegrammer-0.4.2}/ts/lib/log.ts +0 -0
  48. {claude_code_telegrammer-0.4.1 → claude_code_telegrammer-0.4.2}/ts/lib/store.ts +0 -0
  49. {claude_code_telegrammer-0.4.1 → claude_code_telegrammer-0.4.2}/ts/lib/telegram-api.ts +0 -0
  50. {claude_code_telegrammer-0.4.1 → claude_code_telegrammer-0.4.2}/ts/lib/tools.ts +0 -0
  51. {claude_code_telegrammer-0.4.1 → claude_code_telegrammer-0.4.2}/ts/package.json +0 -0
  52. {claude_code_telegrammer-0.4.1 → claude_code_telegrammer-0.4.2}/ts/telegram-server.ts +0 -0
  53. {claude_code_telegrammer-0.4.1 → claude_code_telegrammer-0.4.2}/ts/test/access.test.ts +0 -0
  54. {claude_code_telegrammer-0.4.1 → claude_code_telegrammer-0.4.2}/ts/test/config.test.ts +0 -0
  55. {claude_code_telegrammer-0.4.1 → claude_code_telegrammer-0.4.2}/ts/test/lock.test.ts +0 -0
  56. {claude_code_telegrammer-0.4.1 → claude_code_telegrammer-0.4.2}/ts/test/preload.ts +0 -0
  57. {claude_code_telegrammer-0.4.1 → claude_code_telegrammer-0.4.2}/ts/test/store.test.ts +0 -0
  58. {claude_code_telegrammer-0.4.1 → claude_code_telegrammer-0.4.2}/ts/test/telegram-api.test.ts +0 -0
@@ -0,0 +1,20 @@
1
+ # Claude Code Telegrammer — Environment Variables
2
+ # Copy to .env and fill in your values
3
+
4
+ # Required: Telegram bot token from @BotFather
5
+ CLAUDE_CODE_TELEGRAMMER_TELEGRAM_BOT_TOKEN=123456789:AAH...
6
+
7
+ # Required: Comma-separated Telegram user IDs allowed to send messages
8
+ CLAUDE_CODE_TELEGRAMMER_TELEGRAM_ALLOWED_USERS=YOUR_TELEGRAM_USER_ID
9
+
10
+ # Optional: State directory (default: ~/.claude-code-telegrammer)
11
+ CLAUDE_CODE_TELEGRAMMER_TELEGRAM_STATE_DIR=~/.claude-code-telegrammer
12
+
13
+ # Optional: Hostname for message metadata (default: os.hostname())
14
+ CLAUDE_CODE_TELEGRAMMER_TELEGRAM_HOST_NAME=
15
+
16
+ # Optional: Project identifier for message metadata (default: cwd)
17
+ CLAUDE_CODE_TELEGRAMMER_TELEGRAM_PROJECT=
18
+
19
+ # Optional: Agent ID for message metadata (default: 'telegram')
20
+ CLAUDE_CODE_TELEGRAMMER_TELEGRAM_AGENT_ID=
@@ -0,0 +1,14 @@
1
+ {
2
+ "mcpServers": {
3
+ "claude-code-telegrammer": {
4
+ "type": "stdio",
5
+ "command": "bun",
6
+ "args": ["run", "/path/to/claude-code-telegrammer/ts/telegram-server.ts"],
7
+ "env": {
8
+ "CLAUDE_CODE_TELEGRAMMER_TELEGRAM_BOT_TOKEN": "123456789:AAH...",
9
+ "CLAUDE_CODE_TELEGRAMMER_TELEGRAM_ALLOWED_USERS": "YOUR_TELEGRAM_USER_ID",
10
+ "CLAUDE_CODE_TELEGRAMMER_TELEGRAM_STATE_DIR": "~/.claude-code-telegrammer"
11
+ }
12
+ }
13
+ }
14
+ }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: claude-code-telegrammer
3
- Version: 0.4.1
3
+ Version: 0.4.2
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
@@ -140,34 +140,36 @@ Description-Content-Type: text/markdown
140
140
 
141
141
  ### Important: Bot Token Exclusivity
142
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.
143
+ This MCP server **must be the sole consumer** of its configured Telegram bot token. The Telegram Bot API allows only one `getUpdates` long-polling connection per token.
144
144
 
145
- **If you see 409 errors:**
146
- - Disable the other polling component, **or**
147
- - Use a separate bot token for each component
145
+ **What happens with duplicate consumers:**
146
+
147
+ | Scenario | Symptom | Detection |
148
+ |----------|---------|-----------|
149
+ | Two pollers start simultaneously | One gets 409, the other wins silently | The loser sees `409 Conflict` in logs |
150
+ | Two pollers start sequentially | Both appear to work, but only one receives messages | **No error** — the other poller gets empty responses forever |
151
+ | Webhook active + poller | Poller gets nothing | **No error** — Telegram ignores `getUpdates` when webhook is set |
152
+
153
+ **Why 409 detection alone is insufficient:** The Telegram API does not reliably return 409 for all conflict cases. When two consumers poll sequentially (not overlapping), both connections succeed — one simply receives all messages while the other gets none, with no error. The server performs a `timeout=3` preflight check at startup to catch overlapping polls, but this cannot detect the sequential case.
154
+
155
+ **If messages aren't arriving:**
156
+ 1. Check if another process is polling the same token: `ps aux | grep telegram-server`
157
+ 2. Check if a webhook is set: `curl https://api.telegram.org/bot<TOKEN>/getWebhookInfo`
158
+ 3. Use a separate bot token per component (recommended)
159
+ 4. Or disable the other consumer
148
160
 
149
161
  ## Installation
150
162
 
151
163
  ### Prerequisites
152
164
 
153
- - Python >= 3.10 and GNU Screen (for watchdog/CLI)
154
165
  - [Bun](https://bun.sh/) >= 1.0 (for the MCP server)
166
+ - GNU Screen (for watchdog, optional)
155
167
 
156
168
  ### Install
157
169
 
158
- ```bash
159
- pip install claude-code-telegrammer
160
- ```
161
-
162
- Or from source:
163
-
164
170
  ```bash
165
171
  git clone https://github.com/ywatanabe1989/claude-code-telegrammer.git
166
- cd claude-code-telegrammer
167
- pip install -e .
168
-
169
- # Install TypeScript dependencies for the MCP server
170
- cd ts && bun install
172
+ cd claude-code-telegrammer/ts && bun install
171
173
  ```
172
174
 
173
175
  ## Quickstart
@@ -220,7 +222,7 @@ claude \
220
222
  --dangerously-load-development-channels server:claude-code-telegrammer
221
223
  ```
222
224
 
223
- For full agent orchestration (screen sessions, watchdog, YAML configs), see [scitex-agent-container](https://github.com/ywatanabe1989/scitex-agent-container).
225
+ You should see `Listening for channel messages from: server:claude-code-telegrammer` in the Claude Code TUI. Send a message from Telegram to your bot — Claude Code will receive it as a channel notification.
224
226
 
225
227
  ## Interfaces
226
228
 
@@ -107,34 +107,36 @@
107
107
 
108
108
  ### Important: Bot Token Exclusivity
109
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.
110
+ This MCP server **must be the sole consumer** of its configured Telegram bot token. The Telegram Bot API allows only one `getUpdates` long-polling connection per token.
111
111
 
112
- **If you see 409 errors:**
113
- - Disable the other polling component, **or**
114
- - Use a separate bot token for each component
112
+ **What happens with duplicate consumers:**
113
+
114
+ | Scenario | Symptom | Detection |
115
+ |----------|---------|-----------|
116
+ | Two pollers start simultaneously | One gets 409, the other wins silently | The loser sees `409 Conflict` in logs |
117
+ | Two pollers start sequentially | Both appear to work, but only one receives messages | **No error** — the other poller gets empty responses forever |
118
+ | Webhook active + poller | Poller gets nothing | **No error** — Telegram ignores `getUpdates` when webhook is set |
119
+
120
+ **Why 409 detection alone is insufficient:** The Telegram API does not reliably return 409 for all conflict cases. When two consumers poll sequentially (not overlapping), both connections succeed — one simply receives all messages while the other gets none, with no error. The server performs a `timeout=3` preflight check at startup to catch overlapping polls, but this cannot detect the sequential case.
121
+
122
+ **If messages aren't arriving:**
123
+ 1. Check if another process is polling the same token: `ps aux | grep telegram-server`
124
+ 2. Check if a webhook is set: `curl https://api.telegram.org/bot<TOKEN>/getWebhookInfo`
125
+ 3. Use a separate bot token per component (recommended)
126
+ 4. Or disable the other consumer
115
127
 
116
128
  ## Installation
117
129
 
118
130
  ### Prerequisites
119
131
 
120
- - Python >= 3.10 and GNU Screen (for watchdog/CLI)
121
132
  - [Bun](https://bun.sh/) >= 1.0 (for the MCP server)
133
+ - GNU Screen (for watchdog, optional)
122
134
 
123
135
  ### Install
124
136
 
125
- ```bash
126
- pip install claude-code-telegrammer
127
- ```
128
-
129
- Or from source:
130
-
131
137
  ```bash
132
138
  git clone https://github.com/ywatanabe1989/claude-code-telegrammer.git
133
- cd claude-code-telegrammer
134
- pip install -e .
135
-
136
- # Install TypeScript dependencies for the MCP server
137
- cd ts && bun install
139
+ cd claude-code-telegrammer/ts && bun install
138
140
  ```
139
141
 
140
142
  ## Quickstart
@@ -187,7 +189,7 @@ claude \
187
189
  --dangerously-load-development-channels server:claude-code-telegrammer
188
190
  ```
189
191
 
190
- For full agent orchestration (screen sessions, watchdog, YAML configs), see [scitex-agent-container](https://github.com/ywatanabe1989/scitex-agent-container).
192
+ You should see `Listening for channel messages from: server:claude-code-telegrammer` in the Claude Code TUI. Send a message from Telegram to your bot — Claude Code will receive it as a channel notification.
191
193
 
192
194
  ## Interfaces
193
195
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "claude-code-telegrammer"
7
- version = "0.4.1"
7
+ version = "0.4.2"
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"
@@ -0,0 +1,7 @@
1
+ """Claude Code Telegrammer -- custom Telegram MCP server + TUI auto-responder for Claude Code."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from importlib.metadata import version as _get_version
6
+
7
+ __version__ = _get_version(__name__)
@@ -56,6 +56,39 @@ export async function startPolling(mcp: Server): Promise<void> {
56
56
  log("poller", `getMe failed: ${err}`);
57
57
  }
58
58
 
59
+ // Preflight: try a short long-poll to detect competing consumers.
60
+ // timeout=0 is instant and won't collide — we need timeout>0 to trigger
61
+ // the 409 if another consumer is already in a long-poll.
62
+ log("poller", "preflight: testing for competing consumers (3s)...");
63
+ try {
64
+ await tgApi("getUpdates", { offset: updateOffset, timeout: 3, limit: 1 });
65
+ log("poller", "preflight OK — no competing consumers detected");
66
+ } catch (err) {
67
+ const errMsg = err instanceof Error ? err.message : String(err);
68
+ if (errMsg.includes("409")) {
69
+ const fatalMsg =
70
+ "FATAL: 409 Conflict on startup — another process is already polling this bot token. " +
71
+ "Only one getUpdates consumer is allowed per token. " +
72
+ "Stop the other consumer first, or use a different bot token. " +
73
+ "Refusing to start.";
74
+ log("poller", fatalMsg);
75
+ // Notify the agent so it knows Telegram is NOT connected
76
+ mcp
77
+ .notification({
78
+ method: "notifications/claude/channel",
79
+ params: {
80
+ content: fatalMsg,
81
+ meta: { source: "telegram", type: "error" },
82
+ },
83
+ })
84
+ .catch(() => {});
85
+ polling = false;
86
+ return;
87
+ }
88
+ // Non-409 errors are OK to proceed (e.g., network hiccup)
89
+ log("poller", `preflight warning: ${errMsg} (proceeding anyway)`);
90
+ }
91
+
59
92
  while (polling) {
60
93
  try {
61
94
  const updates = await tgApi("getUpdates", {
@@ -85,14 +118,23 @@ export async function startPolling(mcp: Server): Promise<void> {
85
118
  } catch (err) {
86
119
  const errMsg = err instanceof Error ? err.message : String(err);
87
120
  if (errMsg.includes("409")) {
88
- log(
89
- "poller",
121
+ const conflictMsg =
90
122
  "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
- );
95
- await new Promise((r) => setTimeout(r, 5000));
123
+ "Only one getUpdates consumer is allowed per token. " +
124
+ "Telegram connection is DOWN. Stop the other consumer or use a different bot token.";
125
+ log("poller", conflictMsg);
126
+ mcp
127
+ .notification({
128
+ method: "notifications/claude/channel",
129
+ params: {
130
+ content: conflictMsg,
131
+ meta: { source: "telegram", type: "error" },
132
+ },
133
+ })
134
+ .catch(() => {});
135
+ // Stop polling — don't retry, the agent needs to fix this
136
+ polling = false;
137
+ return;
96
138
  } else {
97
139
  log("poller", `getUpdates error: ${errMsg}. Retrying in 3s...`);
98
140
  await new Promise((r) => setTimeout(r, 3000));
@@ -1,35 +0,0 @@
1
- """Claude Code Telegrammer -- screen-based auto-responder for Claude Code TUI."""
2
-
3
- from __future__ import annotations
4
-
5
- import pathlib
6
-
7
- from importlib.metadata import version as _get_version
8
-
9
- __version__ = _get_version(__name__)
10
-
11
- # Package directory (works for both editable and regular installs)
12
- _PKG_DIR = pathlib.Path(__file__).resolve().parent
13
-
14
-
15
- def get_bin_path(script_name: str) -> str:
16
- """Return absolute path to an installed bash script in bin/.
17
-
18
- Searches two locations:
19
- 1. <package>/bin/<script> (symlinked or copied during install)
20
- 2. <repo_root>/bin/<script> (editable install fallback)
21
- """
22
- # Primary: inside package (symlink or copied)
23
- path = _PKG_DIR / "bin" / script_name
24
- if path.exists():
25
- return str(path)
26
-
27
- # Fallback: repo root (editable install, src layout)
28
- repo_root = _PKG_DIR.parent.parent
29
- path = repo_root / "bin" / script_name
30
- if path.exists():
31
- return str(path)
32
-
33
- raise FileNotFoundError(
34
- f"Script '{script_name}' not found in package bin/ or repo root bin/"
35
- )