bub 0.3.4__tar.gz → 0.3.5__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.
- {bub-0.3.4 → bub-0.3.5}/PKG-INFO +5 -2
- {bub-0.3.4 → bub-0.3.5}/README.md +4 -1
- {bub-0.3.4 → bub-0.3.5}/pyproject.toml +1 -1
- {bub-0.3.4 → bub-0.3.5}/src/bub/__init__.py +1 -1
- {bub-0.3.4 → bub-0.3.5}/src/bub/builtin/cli.py +60 -5
- {bub-0.3.4 → bub-0.3.5}/src/bub/builtin/hook_impl.py +1 -2
- {bub-0.3.4 → bub-0.3.5}/src/bub/builtin/shell_manager.py +2 -1
- {bub-0.3.4 → bub-0.3.5}/src/bub/builtin/tools.py +2 -2
- {bub-0.3.4 → bub-0.3.5}/src/skills/telegram/SKILL.md +113 -125
- {bub-0.3.4 → bub-0.3.5}/src/skills/telegram/scripts/telegram_edit.py +106 -105
- {bub-0.3.4 → bub-0.3.5}/src/skills/telegram/scripts/telegram_send.py +175 -175
- bub-0.3.5/tests/test_builtin_cli.py +150 -0
- bub-0.3.4/tests/test_builtin_cli.py +0 -71
- {bub-0.3.4 → bub-0.3.5}/.gitignore +0 -0
- {bub-0.3.4 → bub-0.3.5}/LICENSE +0 -0
- {bub-0.3.4 → bub-0.3.5}/src/bub/__main__.py +0 -0
- {bub-0.3.4 → bub-0.3.5}/src/bub/builtin/__init__.py +0 -0
- {bub-0.3.4 → bub-0.3.5}/src/bub/builtin/agent.py +2 -2
- {bub-0.3.4 → bub-0.3.5}/src/bub/builtin/auth.py +0 -0
- {bub-0.3.4 → bub-0.3.5}/src/bub/builtin/context.py +0 -0
- {bub-0.3.4 → bub-0.3.5}/src/bub/builtin/settings.py +0 -0
- {bub-0.3.4 → bub-0.3.5}/src/bub/builtin/store.py +0 -0
- {bub-0.3.4 → bub-0.3.5}/src/bub/builtin/tape.py +0 -0
- {bub-0.3.4 → bub-0.3.5}/src/bub/channels/__init__.py +0 -0
- {bub-0.3.4 → bub-0.3.5}/src/bub/channels/base.py +0 -0
- {bub-0.3.4 → bub-0.3.5}/src/bub/channels/cli/__init__.py +0 -0
- {bub-0.3.4 → bub-0.3.5}/src/bub/channels/cli/renderer.py +0 -0
- {bub-0.3.4 → bub-0.3.5}/src/bub/channels/handler.py +0 -0
- {bub-0.3.4 → bub-0.3.5}/src/bub/channels/manager.py +0 -0
- {bub-0.3.4 → bub-0.3.5}/src/bub/channels/message.py +0 -0
- {bub-0.3.4 → bub-0.3.5}/src/bub/channels/telegram.py +0 -0
- {bub-0.3.4 → bub-0.3.5}/src/bub/envelope.py +0 -0
- {bub-0.3.4 → bub-0.3.5}/src/bub/framework.py +0 -0
- {bub-0.3.4 → bub-0.3.5}/src/bub/hook_runtime.py +0 -0
- {bub-0.3.4 → bub-0.3.5}/src/bub/hookspecs.py +0 -0
- {bub-0.3.4 → bub-0.3.5}/src/bub/skills.py +0 -0
- {bub-0.3.4 → bub-0.3.5}/src/bub/tools.py +0 -0
- {bub-0.3.4 → bub-0.3.5}/src/bub/types.py +0 -0
- {bub-0.3.4 → bub-0.3.5}/src/bub/utils.py +0 -0
- {bub-0.3.4 → bub-0.3.5}/src/skills/README.md +0 -0
- {bub-0.3.4 → bub-0.3.5}/src/skills/gh/SKILL.md +0 -0
- {bub-0.3.4 → bub-0.3.5}/src/skills/skill-creator/SKILL.md +0 -0
- {bub-0.3.4 → bub-0.3.5}/src/skills/skill-creator/license.txt +0 -0
- {bub-0.3.4 → bub-0.3.5}/src/skills/skill-creator/scripts/init_skill.py +0 -0
- {bub-0.3.4 → bub-0.3.5}/src/skills/skill-creator/scripts/quick_validate.py +0 -0
- {bub-0.3.4 → bub-0.3.5}/tests/test_builtin_agent.py +0 -0
- {bub-0.3.4 → bub-0.3.5}/tests/test_builtin_hook_impl.py +0 -0
- {bub-0.3.4 → bub-0.3.5}/tests/test_builtin_tools.py +0 -0
- {bub-0.3.4 → bub-0.3.5}/tests/test_channels.py +0 -0
- {bub-0.3.4 → bub-0.3.5}/tests/test_cli_help.py +0 -0
- {bub-0.3.4 → bub-0.3.5}/tests/test_envelope.py +0 -0
- {bub-0.3.4 → bub-0.3.5}/tests/test_file_tape_store_entry_ids.py +0 -0
- {bub-0.3.4 → bub-0.3.5}/tests/test_fork_store_merge_back.py +0 -0
- {bub-0.3.4 → bub-0.3.5}/tests/test_framework.py +0 -0
- {bub-0.3.4 → bub-0.3.5}/tests/test_hook_runtime.py +0 -0
- {bub-0.3.4 → bub-0.3.5}/tests/test_image_message.py +0 -0
- {bub-0.3.4 → bub-0.3.5}/tests/test_settings.py +0 -0
- {bub-0.3.4 → bub-0.3.5}/tests/test_skills.py +0 -0
- {bub-0.3.4 → bub-0.3.5}/tests/test_subagent_tool.py +0 -0
- {bub-0.3.4 → bub-0.3.5}/tests/test_tape_search_output.py +0 -0
- {bub-0.3.4 → bub-0.3.5}/tests/test_tools.py +0 -0
- {bub-0.3.4 → bub-0.3.5}/tests/test_utils.py +0 -0
{bub-0.3.4 → bub-0.3.5}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bub
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.5
|
|
4
4
|
Summary: A common shape for agents that live alongside people.
|
|
5
5
|
Project-URL: Homepage, https://bub.build
|
|
6
6
|
Project-URL: Repository, https://github.com/bubbuild/bub
|
|
@@ -130,11 +130,14 @@ See the [Extension Guide](https://bub.build/extension-guide/) for hook semantics
|
|
|
130
130
|
| `bub chat` | Interactive REPL |
|
|
131
131
|
| `bub run MESSAGE` | One-shot turn |
|
|
132
132
|
| `bub gateway` | Channel listener (Telegram, etc.) |
|
|
133
|
+
| `bub install` | Install or sync Bub plugin deps |
|
|
134
|
+
| `bub update` | Upgrade Bub plugin deps |
|
|
133
135
|
| `bub login openai` | OpenAI Codex OAuth |
|
|
134
|
-
| `bub hooks` | Print hook-to-plugin bindings |
|
|
135
136
|
|
|
136
137
|
Lines starting with `,` enter internal command mode (`,help`, `,skill name=my-skill`, `,fs.read path=README.md`).
|
|
137
138
|
|
|
139
|
+
`bub hooks` still exists for diagnostics, but it is hidden from top-level help. `bub install` and `bub update` manage a separate uv project for Bub plugins, defaulting to `~/.bub/bub-project` or `BUB_PROJECT`.
|
|
140
|
+
|
|
138
141
|
## Configuration
|
|
139
142
|
|
|
140
143
|
| Variable | Default | Description |
|
|
@@ -96,11 +96,14 @@ See the [Extension Guide](https://bub.build/extension-guide/) for hook semantics
|
|
|
96
96
|
| `bub chat` | Interactive REPL |
|
|
97
97
|
| `bub run MESSAGE` | One-shot turn |
|
|
98
98
|
| `bub gateway` | Channel listener (Telegram, etc.) |
|
|
99
|
+
| `bub install` | Install or sync Bub plugin deps |
|
|
100
|
+
| `bub update` | Upgrade Bub plugin deps |
|
|
99
101
|
| `bub login openai` | OpenAI Codex OAuth |
|
|
100
|
-
| `bub hooks` | Print hook-to-plugin bindings |
|
|
101
102
|
|
|
102
103
|
Lines starting with `,` enter internal command mode (`,help`, `,skill name=my-skill`, `,fs.read path=README.md`).
|
|
103
104
|
|
|
105
|
+
`bub hooks` still exists for diagnostics, but it is hidden from top-level help. `bub install` and `bub update` manage a separate uv project for Bub plugins, defaulting to `~/.bub/bub-project` or `BUB_PROJECT`.
|
|
106
|
+
|
|
104
107
|
## Configuration
|
|
105
108
|
|
|
106
109
|
| Variable | Default | Description |
|
|
@@ -4,11 +4,15 @@
|
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
|
|
6
6
|
import asyncio
|
|
7
|
+
import json
|
|
7
8
|
import os
|
|
8
9
|
import subprocess
|
|
9
10
|
import sys
|
|
10
11
|
from functools import lru_cache
|
|
12
|
+
from importlib import metadata
|
|
11
13
|
from pathlib import Path
|
|
14
|
+
from urllib.parse import unquote, urlsplit
|
|
15
|
+
from urllib.request import url2pathname
|
|
12
16
|
|
|
13
17
|
import typer
|
|
14
18
|
|
|
@@ -148,15 +152,67 @@ def _build_requirement(spec: str) -> str:
|
|
|
148
152
|
return f"git+https://github.com/{repo}.git{ref}"
|
|
149
153
|
else:
|
|
150
154
|
# Assume it's a package name in bub-contrib
|
|
151
|
-
name,
|
|
152
|
-
|
|
153
|
-
|
|
155
|
+
name, has_ref, ref = spec.partition("@")
|
|
156
|
+
if has_ref:
|
|
157
|
+
ref = f"@{ref}"
|
|
158
|
+
return f"git+{BUB_CONTRIB_REPO}{ref}#subdirectory=packages/{name}"
|
|
159
|
+
else: # PyPI package name
|
|
160
|
+
return name
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _build_local_requirement_path(url: str, subdirectory: str | None = None) -> str | None:
|
|
164
|
+
parsed = urlsplit(url)
|
|
165
|
+
if parsed.scheme != "file":
|
|
166
|
+
return None
|
|
167
|
+
|
|
168
|
+
path = parsed.path
|
|
169
|
+
if parsed.netloc and parsed.netloc != "localhost":
|
|
170
|
+
path = f"//{parsed.netloc}{path}"
|
|
171
|
+
local_path = Path(url2pathname(unquote(path)))
|
|
172
|
+
if subdirectory:
|
|
173
|
+
local_path /= subdirectory
|
|
174
|
+
return os.fspath(local_path)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _build_bub_requirement() -> list[str]:
|
|
178
|
+
dist = metadata.distribution("bub")
|
|
179
|
+
dist_name = dist.name
|
|
180
|
+
direct_url_text = dist.read_text("direct_url.json")
|
|
181
|
+
if not direct_url_text:
|
|
182
|
+
return [dist_name]
|
|
183
|
+
|
|
184
|
+
direct_url = json.loads(direct_url_text)
|
|
185
|
+
requirement_url = str(direct_url["url"])
|
|
186
|
+
subdirectory = direct_url.get("subdirectory")
|
|
187
|
+
normalized_subdirectory = subdirectory if isinstance(subdirectory, str) and subdirectory else None
|
|
188
|
+
|
|
189
|
+
local_path = _build_local_requirement_path(requirement_url, normalized_subdirectory)
|
|
190
|
+
if local_path is not None:
|
|
191
|
+
dir_info = direct_url.get("dir_info")
|
|
192
|
+
editable = isinstance(dir_info, dict) and bool(dir_info.get("editable"))
|
|
193
|
+
return ["--editable", local_path] if editable else [local_path]
|
|
194
|
+
|
|
195
|
+
vcs_info = direct_url.get("vcs_info")
|
|
196
|
+
if isinstance(vcs_info, dict):
|
|
197
|
+
vcs = vcs_info.get("vcs")
|
|
198
|
+
requested_revision = vcs_info.get("requested_revision")
|
|
199
|
+
if isinstance(vcs, str) and vcs:
|
|
200
|
+
requirement_url = f"{vcs}+{requirement_url}"
|
|
201
|
+
if isinstance(requested_revision, str) and requested_revision:
|
|
202
|
+
requirement_url = f"{requirement_url}@{requested_revision}"
|
|
203
|
+
|
|
204
|
+
if normalized_subdirectory:
|
|
205
|
+
requirement_url = f"{requirement_url}#subdirectory={normalized_subdirectory}"
|
|
206
|
+
|
|
207
|
+
return [requirement_url]
|
|
154
208
|
|
|
155
209
|
|
|
156
210
|
def _ensure_project(project: Path) -> None:
|
|
157
211
|
if (project / "pyproject.toml").is_file():
|
|
158
212
|
return
|
|
159
213
|
_uv("init", "--bare", "--name", "bub-project", "--app", cwd=project)
|
|
214
|
+
bub_requirement = _build_bub_requirement()
|
|
215
|
+
_uv("add", "--active", "--no-sync", *bub_requirement, cwd=project)
|
|
160
216
|
|
|
161
217
|
|
|
162
218
|
def install(
|
|
@@ -180,8 +236,7 @@ def uninstall(
|
|
|
180
236
|
) -> None:
|
|
181
237
|
"""Uninstall a plugin from Bub's environment."""
|
|
182
238
|
_ensure_project(project)
|
|
183
|
-
_uv("remove", "--active",
|
|
184
|
-
_uv("sync", "--active", "--frozen", "--inexact", cwd=project)
|
|
239
|
+
_uv("remove", "--active", *packages, cwd=project)
|
|
185
240
|
|
|
186
241
|
|
|
187
242
|
def update(
|
|
@@ -119,8 +119,7 @@ class BuiltinImpl:
|
|
|
119
119
|
app.command("hooks", hidden=True)(cli.list_hooks)
|
|
120
120
|
app.command("gateway")(cli.gateway)
|
|
121
121
|
app.command("install")(cli.install)
|
|
122
|
-
|
|
123
|
-
# app.command("uninstall")(cli.uninstall)
|
|
122
|
+
app.command("uninstall")(cli.uninstall)
|
|
124
123
|
app.command("update")(cli.update)
|
|
125
124
|
|
|
126
125
|
def _read_agents_file(self, state: State) -> str:
|
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import contextlib
|
|
5
5
|
import os
|
|
6
|
+
import shutil
|
|
6
7
|
import uuid
|
|
7
8
|
from dataclasses import dataclass, field
|
|
8
9
|
|
|
@@ -39,7 +40,7 @@ class ShellManager:
|
|
|
39
40
|
cwd=cwd,
|
|
40
41
|
stdout=asyncio.subprocess.PIPE,
|
|
41
42
|
stderr=asyncio.subprocess.PIPE,
|
|
42
|
-
executable="
|
|
43
|
+
executable=(shutil.which("bash") or shutil.which("sh")) if os.name != "nt" else None,
|
|
43
44
|
)
|
|
44
45
|
shell = ManagedShell(shell_id=f"bash-{uuid.uuid4().hex[:8]}", cmd=cmd, cwd=cwd, process=process)
|
|
45
46
|
shell.read_tasks.extend([
|
|
@@ -42,9 +42,9 @@ class SearchInput(BaseModel):
|
|
|
42
42
|
limit: int = Field(20, description="Maximum number of search results to return.")
|
|
43
43
|
start: str | None = Field(None, description="Optional start date to filter entries (ISO format).")
|
|
44
44
|
end: str | None = Field(None, description="Optional end date to filter entries (ISO format).")
|
|
45
|
-
kinds: list[
|
|
45
|
+
kinds: list[str] = Field(
|
|
46
46
|
default=["message", "tool_result"],
|
|
47
|
-
description="Optional list of entry kinds to filter search results.",
|
|
47
|
+
description="Optional list of entry kinds to filter search results. Can include 'event', 'anchor', 'system', 'message', 'tool_call', 'tool_result'.",
|
|
48
48
|
)
|
|
49
49
|
|
|
50
50
|
|
|
@@ -1,125 +1,113 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: telegram
|
|
3
|
-
description: |
|
|
4
|
-
Telegram Bot skill for sending and editing Telegram messages via Bot API.
|
|
5
|
-
Use when Bub needs to: (1) Send a message to a Telegram user/group/channel,
|
|
6
|
-
(2) Reply to a specific Telegram message with reply_to_message_id,
|
|
7
|
-
(3) Edit an existing Telegram message, or (4) Push proactive Telegram notifications.
|
|
8
|
-
metadata:
|
|
9
|
-
channel: telegram
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
# Telegram Skill
|
|
13
|
-
|
|
14
|
-
Agent-facing execution guide for Telegram outbound communication.
|
|
15
|
-
|
|
16
|
-
Assumption: `BUB_TELEGRAM_TOKEN` is already available.
|
|
17
|
-
|
|
18
|
-
## Required Inputs
|
|
19
|
-
Collect these before execution:
|
|
20
|
-
|
|
21
|
-
- `chat_id` (required)
|
|
22
|
-
- `message_id` (required for edit or reply)
|
|
23
|
-
- message content (required for send/edit)
|
|
24
|
-
- `reply_to_message_id` (required when you need a threaded reply)
|
|
25
|
-
|
|
26
|
-
## Execution Policy
|
|
27
|
-
|
|
28
|
-
1. If handling a Telegram message and `message_id` is known, send a reply message with `--reply-to`.
|
|
29
|
-
2. If there is no message to reply to, send a normal message to `chat_id`.
|
|
30
|
-
3. For long-running tasks, optionally send one progress message, then edit that same message for final status.
|
|
31
|
-
4.
|
|
32
|
-
5. Avoid emitting HTML tags in message content; use Markdown for formatting instead.
|
|
33
|
-
|
|
34
|
-
## Bot to co-Bot Communication
|
|
35
|
-
|
|
36
|
-
In Telegram groups, communicate with another bot using only these patterns:
|
|
37
|
-
|
|
38
|
-
1. Reply directly to the other bot's message when `message_id` is available.
|
|
39
|
-
2. Use an explicit command mention such as `/command@OtherBot` when you need to invoke that bot intentionally.
|
|
40
|
-
3. Do not assume free-form group text will reach another bot.
|
|
41
|
-
|
|
42
|
-
## Active Response Policy
|
|
43
|
-
|
|
44
|
-
When this skill is in scope, prefer proactive and timely Telegram updates:
|
|
45
|
-
|
|
46
|
-
- Send an immediate acknowledgment for newly assigned tasks
|
|
47
|
-
- Send progress updates for long-running operations using message edits
|
|
48
|
-
- Send completion notifications when work finishes
|
|
49
|
-
- Send important status or failure notifications without waiting for follow-up prompts
|
|
50
|
-
- If execution is blocked or fails, send a problem report immediately with cause, impact, and next action
|
|
51
|
-
|
|
52
|
-
Recommended pattern:
|
|
53
|
-
|
|
54
|
-
1. Send a short acknowledgment reply
|
|
55
|
-
2. Continue processing
|
|
56
|
-
3. If blocked, edit or send an issue update immediately
|
|
57
|
-
4. Edit the acknowledgment message with final result when possible
|
|
58
|
-
|
|
59
|
-
## Voice Message Policy
|
|
60
|
-
|
|
61
|
-
When the inbound Telegram message is voice:
|
|
62
|
-
|
|
63
|
-
1. Transcribe the voice input first (use STT skill if available)
|
|
64
|
-
2. Prepare response content based on transcription
|
|
65
|
-
3. Prefer voice response output (use TTS skill if available)
|
|
66
|
-
4. If voice output is unavailable, send a concise text fallback and state limitation
|
|
67
|
-
|
|
68
|
-
## Reaction Policy
|
|
69
|
-
|
|
70
|
-
When an inbound Telegram message warrants acknowledgment but does not merit a full reply, use a Telegram reaction as the response.
|
|
71
|
-
But when any explanation or details are needed, use a normal reply instead.
|
|
72
|
-
|
|
73
|
-
## Command Templates
|
|
74
|
-
|
|
75
|
-
Paths are relative to this skill directory.
|
|
76
|
-
|
|
77
|
-
```bash
|
|
78
|
-
# Send
|
|
79
|
-
uv run ${SKILL_DIR}/scripts/telegram_send.py
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
- `--chat-id`, `-c`: required, supports comma-separated ids
|
|
116
|
-
- `--message`, `-m`: required
|
|
117
|
-
- `--reply-to`, `-r`: optional
|
|
118
|
-
- `--token`, `-t`: optional (normally not needed)
|
|
119
|
-
|
|
120
|
-
### `telegram_edit.py`
|
|
121
|
-
|
|
122
|
-
- `--chat-id`, `-c`: required
|
|
123
|
-
- `--message-id`, `-m`: required
|
|
124
|
-
- `--text`, `-t`: required
|
|
125
|
-
- `--token`: optional (normally not needed)
|
|
1
|
+
---
|
|
2
|
+
name: telegram
|
|
3
|
+
description: |
|
|
4
|
+
Telegram Bot skill for sending and editing Telegram messages via Bot API.
|
|
5
|
+
Use when Bub needs to: (1) Send a message to a Telegram user/group/channel,
|
|
6
|
+
(2) Reply to a specific Telegram message with reply_to_message_id,
|
|
7
|
+
(3) Edit an existing Telegram message, or (4) Push proactive Telegram notifications.
|
|
8
|
+
metadata:
|
|
9
|
+
channel: telegram
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Telegram Skill
|
|
13
|
+
|
|
14
|
+
Agent-facing execution guide for Telegram outbound communication.
|
|
15
|
+
|
|
16
|
+
Assumption: `BUB_TELEGRAM_TOKEN` is already available.
|
|
17
|
+
|
|
18
|
+
## Required Inputs
|
|
19
|
+
Collect these before execution:
|
|
20
|
+
|
|
21
|
+
- `chat_id` (required)
|
|
22
|
+
- `message_id` (required for edit or reply)
|
|
23
|
+
- message content (required for send/edit)
|
|
24
|
+
- `reply_to_message_id` (required when you need a threaded reply)
|
|
25
|
+
|
|
26
|
+
## Execution Policy
|
|
27
|
+
|
|
28
|
+
1. If handling a Telegram message and `message_id` is known, send a reply message with `--reply-to`.
|
|
29
|
+
2. If there is no message to reply to, send a normal message to `chat_id`.
|
|
30
|
+
3. For long-running tasks, optionally send one progress message, then edit that same message for final status.
|
|
31
|
+
4. **ALWAYS pass message content via stdin using heredoc pipe and `--message -` (or `--text -`).** NEVER embed message text directly in shell arguments — special characters like `'`, `"`, `$`, `!` will be mangled or cause syntax errors.
|
|
32
|
+
5. Avoid emitting HTML tags in message content; use Markdown for formatting instead.
|
|
33
|
+
|
|
34
|
+
## Bot to co-Bot Communication
|
|
35
|
+
|
|
36
|
+
In Telegram groups, communicate with another bot using only these patterns:
|
|
37
|
+
|
|
38
|
+
1. Reply directly to the other bot's message when `message_id` is available.
|
|
39
|
+
2. Use an explicit command mention such as `/command@OtherBot` when you need to invoke that bot intentionally.
|
|
40
|
+
3. Do not assume free-form group text will reach another bot.
|
|
41
|
+
|
|
42
|
+
## Active Response Policy
|
|
43
|
+
|
|
44
|
+
When this skill is in scope, prefer proactive and timely Telegram updates:
|
|
45
|
+
|
|
46
|
+
- Send an immediate acknowledgment for newly assigned tasks
|
|
47
|
+
- Send progress updates for long-running operations using message edits
|
|
48
|
+
- Send completion notifications when work finishes
|
|
49
|
+
- Send important status or failure notifications without waiting for follow-up prompts
|
|
50
|
+
- If execution is blocked or fails, send a problem report immediately with cause, impact, and next action
|
|
51
|
+
|
|
52
|
+
Recommended pattern:
|
|
53
|
+
|
|
54
|
+
1. Send a short acknowledgment reply
|
|
55
|
+
2. Continue processing
|
|
56
|
+
3. If blocked, edit or send an issue update immediately
|
|
57
|
+
4. Edit the acknowledgment message with final result when possible
|
|
58
|
+
|
|
59
|
+
## Voice Message Policy
|
|
60
|
+
|
|
61
|
+
When the inbound Telegram message is voice:
|
|
62
|
+
|
|
63
|
+
1. Transcribe the voice input first (use STT skill if available)
|
|
64
|
+
2. Prepare response content based on transcription
|
|
65
|
+
3. Prefer voice response output (use TTS skill if available)
|
|
66
|
+
4. If voice output is unavailable, send a concise text fallback and state limitation
|
|
67
|
+
|
|
68
|
+
## Reaction Policy
|
|
69
|
+
|
|
70
|
+
When an inbound Telegram message warrants acknowledgment but does not merit a full reply, use a Telegram reaction as the response.
|
|
71
|
+
But when any explanation or details are needed, use a normal reply instead.
|
|
72
|
+
|
|
73
|
+
## Command Templates
|
|
74
|
+
|
|
75
|
+
Paths are relative to this skill directory.
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# Send message (ALWAYS use heredoc stdin, never inline text in arguments)
|
|
79
|
+
cat << 'EOF' | uv run ${SKILL_DIR}/scripts/telegram_send.py --chat-id <CHAT_ID> --message -
|
|
80
|
+
Your message content here.
|
|
81
|
+
Special characters are safe: $100, "quotes", 'apostrophes', !exclamation
|
|
82
|
+
EOF
|
|
83
|
+
|
|
84
|
+
# Reply to a specific message
|
|
85
|
+
cat << 'EOF' | uv run ${SKILL_DIR}/scripts/telegram_send.py --chat-id <CHAT_ID> --reply-to <MESSAGE_ID> --message -
|
|
86
|
+
Reply content here.
|
|
87
|
+
EOF
|
|
88
|
+
|
|
89
|
+
# Edit an existing message
|
|
90
|
+
cat << 'EOF' | uv run ${SKILL_DIR}/scripts/telegram_edit.py --chat-id <CHAT_ID> --message-id <MESSAGE_ID> --text -
|
|
91
|
+
Updated content here.
|
|
92
|
+
EOF
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
When sending message to a bot, either use `--reply-to` argument or pass `--source-is-bot` with `--source-username` otherwise the bot will not receive the message.
|
|
96
|
+
|
|
97
|
+
For other actions that not covered by these scripts, use `curl` to call Telegram Bot API directly with the provided token.
|
|
98
|
+
|
|
99
|
+
## Script Interface Reference
|
|
100
|
+
|
|
101
|
+
### `telegram_send.py`
|
|
102
|
+
|
|
103
|
+
- `--chat-id`, `-c`: required, supports comma-separated ids
|
|
104
|
+
- `--message`, `-m`: required (use `-` to read from stdin)
|
|
105
|
+
- `--reply-to`, `-r`: optional
|
|
106
|
+
- `--token`, `-t`: optional (normally not needed)
|
|
107
|
+
|
|
108
|
+
### `telegram_edit.py`
|
|
109
|
+
|
|
110
|
+
- `--chat-id`, `-c`: required
|
|
111
|
+
- `--message-id`, `-m`: required
|
|
112
|
+
- `--text`, `-t`: required (use `-` to read from stdin)
|
|
113
|
+
- `--token`: optional (normally not needed)
|
|
@@ -1,105 +1,106 @@
|
|
|
1
|
-
#!/usr/bin/env uv run
|
|
2
|
-
# /// script
|
|
3
|
-
# requires-python = ">=3.10"
|
|
4
|
-
# dependencies = [
|
|
5
|
-
# "requests>=2.31.0",
|
|
6
|
-
# "telegramify-markdown>=0.5.0",
|
|
7
|
-
# ]
|
|
8
|
-
# ///
|
|
9
|
-
|
|
10
|
-
"""
|
|
11
|
-
Telegram Bot Message Editor
|
|
12
|
-
|
|
13
|
-
Edit an existing message via Telegram Bot API.
|
|
14
|
-
Uses telegramify_markdown to convert markdown to Telegram MarkdownV2 format.
|
|
15
|
-
|
|
16
|
-
"""
|
|
17
|
-
|
|
18
|
-
import argparse
|
|
19
|
-
import os
|
|
20
|
-
import sys
|
|
21
|
-
|
|
22
|
-
import requests
|
|
23
|
-
|
|
24
|
-
try:
|
|
25
|
-
from telegramify_markdown import markdownify
|
|
26
|
-
except ImportError:
|
|
27
|
-
print("❌ Error: telegramify_markdown not installed. Run: pip install telegramify-markdown")
|
|
28
|
-
sys.exit(1)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def unescape_newlines(text: str) -> str:
|
|
32
|
-
"""
|
|
33
|
-
Convert escaped newline sequences to real newlines.
|
|
34
|
-
Handles \\n -> \n, \\r\\n -> \r\n, etc.
|
|
35
|
-
"""
|
|
36
|
-
# First unescape \\n to real newline
|
|
37
|
-
result = text.replace("\\n", "\n")
|
|
38
|
-
result = result.replace("\\r\\n", "\r\n")
|
|
39
|
-
result = result.replace("\\r", "\r")
|
|
40
|
-
return result
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def edit_message(bot_token: str, chat_id: str, message_id: int, text: str) -> dict:
|
|
44
|
-
"""
|
|
45
|
-
Edit an existing message via Telegram Bot API.
|
|
46
|
-
|
|
47
|
-
Args:
|
|
48
|
-
bot_token: Telegram bot token
|
|
49
|
-
chat_id: Target chat ID
|
|
50
|
-
message_id: ID of the message to edit
|
|
51
|
-
text: New message text (will be converted to MarkdownV2)
|
|
52
|
-
|
|
53
|
-
Returns:
|
|
54
|
-
API response as dict
|
|
55
|
-
"""
|
|
56
|
-
url = f"https://api.telegram.org/bot{bot_token}/editMessageText"
|
|
57
|
-
|
|
58
|
-
# Unescape \\n sequences to real newlines (bash/argparse converts real newlines to \\n)
|
|
59
|
-
text = unescape_newlines(text)
|
|
60
|
-
|
|
61
|
-
# Convert markdown to Telegram MarkdownV2 format
|
|
62
|
-
converted_text = markdownify(text).rstrip("\n")
|
|
63
|
-
|
|
64
|
-
payload = {
|
|
65
|
-
"chat_id": chat_id,
|
|
66
|
-
"message_id": message_id,
|
|
67
|
-
"text": converted_text,
|
|
68
|
-
"parse_mode": "MarkdownV2",
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
response = requests.post(url, json=payload, timeout=30)
|
|
72
|
-
response.raise_for_status()
|
|
73
|
-
|
|
74
|
-
return response.json()
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
def main():
|
|
78
|
-
parser = argparse.ArgumentParser(description="Edit an existing message via Telegram Bot API")
|
|
79
|
-
parser.add_argument("--chat-id", "-c", required=True, help="Target chat ID")
|
|
80
|
-
parser.add_argument("--message-id", "-m", type=int, required=True, help="ID of the message to edit")
|
|
81
|
-
parser.add_argument("--text", "-t", required=True, help="New message text (markdown supported)")
|
|
82
|
-
parser.add_argument("--token", help="Bot token (defaults to BUB_TELEGRAM_TOKEN env var)")
|
|
83
|
-
|
|
84
|
-
args = parser.parse_args()
|
|
85
|
-
|
|
86
|
-
# Get bot token
|
|
87
|
-
bot_token = args.token or os.environ.get("BUB_TELEGRAM_TOKEN")
|
|
88
|
-
if not bot_token:
|
|
89
|
-
print("❌ Error: Bot token required. Set BUB_TELEGRAM_TOKEN env var or use --token")
|
|
90
|
-
sys.exit(1)
|
|
91
|
-
|
|
92
|
-
try:
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
print(f"
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
1
|
+
#!/usr/bin/env uv run
|
|
2
|
+
# /// script
|
|
3
|
+
# requires-python = ">=3.10"
|
|
4
|
+
# dependencies = [
|
|
5
|
+
# "requests>=2.31.0",
|
|
6
|
+
# "telegramify-markdown>=0.5.0",
|
|
7
|
+
# ]
|
|
8
|
+
# ///
|
|
9
|
+
|
|
10
|
+
"""
|
|
11
|
+
Telegram Bot Message Editor
|
|
12
|
+
|
|
13
|
+
Edit an existing message via Telegram Bot API.
|
|
14
|
+
Uses telegramify_markdown to convert markdown to Telegram MarkdownV2 format.
|
|
15
|
+
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import argparse
|
|
19
|
+
import os
|
|
20
|
+
import sys
|
|
21
|
+
|
|
22
|
+
import requests
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
from telegramify_markdown import markdownify
|
|
26
|
+
except ImportError:
|
|
27
|
+
print("❌ Error: telegramify_markdown not installed. Run: pip install telegramify-markdown")
|
|
28
|
+
sys.exit(1)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def unescape_newlines(text: str) -> str:
|
|
32
|
+
"""
|
|
33
|
+
Convert escaped newline sequences to real newlines.
|
|
34
|
+
Handles \\n -> \n, \\r\\n -> \r\n, etc.
|
|
35
|
+
"""
|
|
36
|
+
# First unescape \\n to real newline
|
|
37
|
+
result = text.replace("\\n", "\n")
|
|
38
|
+
result = result.replace("\\r\\n", "\r\n")
|
|
39
|
+
result = result.replace("\\r", "\r")
|
|
40
|
+
return result
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def edit_message(bot_token: str, chat_id: str, message_id: int, text: str) -> dict:
|
|
44
|
+
"""
|
|
45
|
+
Edit an existing message via Telegram Bot API.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
bot_token: Telegram bot token
|
|
49
|
+
chat_id: Target chat ID
|
|
50
|
+
message_id: ID of the message to edit
|
|
51
|
+
text: New message text (will be converted to MarkdownV2)
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
API response as dict
|
|
55
|
+
"""
|
|
56
|
+
url = f"https://api.telegram.org/bot{bot_token}/editMessageText"
|
|
57
|
+
|
|
58
|
+
# Unescape \\n sequences to real newlines (bash/argparse converts real newlines to \\n)
|
|
59
|
+
text = unescape_newlines(text)
|
|
60
|
+
|
|
61
|
+
# Convert markdown to Telegram MarkdownV2 format
|
|
62
|
+
converted_text = markdownify(text).rstrip("\n")
|
|
63
|
+
|
|
64
|
+
payload = {
|
|
65
|
+
"chat_id": chat_id,
|
|
66
|
+
"message_id": message_id,
|
|
67
|
+
"text": converted_text,
|
|
68
|
+
"parse_mode": "MarkdownV2",
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
response = requests.post(url, json=payload, timeout=30)
|
|
72
|
+
response.raise_for_status()
|
|
73
|
+
|
|
74
|
+
return response.json()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def main():
|
|
78
|
+
parser = argparse.ArgumentParser(description="Edit an existing message via Telegram Bot API")
|
|
79
|
+
parser.add_argument("--chat-id", "-c", required=True, help="Target chat ID")
|
|
80
|
+
parser.add_argument("--message-id", "-m", type=int, required=True, help="ID of the message to edit")
|
|
81
|
+
parser.add_argument("--text", "-t", required=True, help="New message text (markdown supported)")
|
|
82
|
+
parser.add_argument("--token", help="Bot token (defaults to BUB_TELEGRAM_TOKEN env var)")
|
|
83
|
+
|
|
84
|
+
args = parser.parse_args()
|
|
85
|
+
|
|
86
|
+
# Get bot token
|
|
87
|
+
bot_token = args.token or os.environ.get("BUB_TELEGRAM_TOKEN")
|
|
88
|
+
if not bot_token:
|
|
89
|
+
print("❌ Error: Bot token required. Set BUB_TELEGRAM_TOKEN env var or use --token")
|
|
90
|
+
sys.exit(1)
|
|
91
|
+
|
|
92
|
+
try:
|
|
93
|
+
text = sys.stdin.read() if args.text == "-" else args.text
|
|
94
|
+
edit_message(bot_token, args.chat_id, args.message_id, text)
|
|
95
|
+
print(f"✅ Message {args.message_id} edited successfully")
|
|
96
|
+
except requests.HTTPError as e:
|
|
97
|
+
print(f"❌ HTTP Error: {e}")
|
|
98
|
+
print(f" Response: {e.response.text}")
|
|
99
|
+
sys.exit(1)
|
|
100
|
+
except Exception as e:
|
|
101
|
+
print(f"❌ Error: {e}")
|
|
102
|
+
sys.exit(1)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
if __name__ == "__main__":
|
|
106
|
+
main()
|