Install
openclaw skills install @bablobanov/telegram-checklistNative Telegram To-Do checklists: create, append, toggle.
openclaw skills install @bablobanov/telegram-checklistCreate and maintain native Telegram checklist objects (checkboxes, progress counter, shared completion) from a Telethon user session. The Bot API sendChecklist works only on behalf of a business account into its private chats with customers - a bot cannot post a checklist into a group or forum topic. MTProto under a user session can, which is what this skill does. Creating a checklist requires Telegram Premium on the acting account; reading or completing one does not. Keep this skill separate from any read-only Telethon reader.
TELETHON_API_ID / TELETHON_API_HASH in ~/.hermes/.env (from my.telegram.org), session file in ~/.hermes/telethon/ (default user.session; override with TELETHON_SESSION)TELETHON_CHECKLIST_CHATS in ~/.hermes/.env, comma-separated entries; -100xxxxxxxxxx allows the whole chat, -100xxxxxxxxxx:33 allows only forum topic 33 of that chat - for every command, reads included (get refuses a checklist outside the allowed topics; list-topics fetches only the allowed topics by id). Only negative ids (groups/channels) are accepted - user peers are out of scope. Topic ids start at 2: the General topic cannot be topic-restricted (allow the whole chat and omit --thread to post to General). Saved Messages (me) is always allowedtelethon>=1.44 (ships the MTProto To-Do types); no bot token neededme plus TELETHON_CHECKLIST_CHATS); the script itself refuses anything else, including a wrong topic when a chat is allowlisted per-topic - on get, append, toggle and list-topics as well as on create; the refusal never names the topic the message is actually in. The allowlist is an explicit, narrow, auditable mechanism - never widen it silently, never treat the script as a general sendershared: true in plan.json){"ok": false, "error": ...} and never invents a message_id. In the rare case the server does not return one, message_id is null with an explicit warning - check the chat before any retry, the list was likely sentpython3 ${HERMES_SKILL_DIR}/telethon_checklist.py list-topics --chat -100...
python3 ${HERMES_SKILL_DIR}/telethon_checklist.py plan --file plan.json
python3 ${HERMES_SKILL_DIR}/telethon_checklist.py create --from-plan plan.json [--dry-run]
python3 ${HERMES_SKILL_DIR}/telethon_checklist.py create --title "<topic>" --task "<a>" --task "<b>" --chat -100... [--thread <topic_id>] [--others-append] [--others-complete] [--dry-run]
python3 ${HERMES_SKILL_DIR}/telethon_checklist.py get --chat -100... --message-id <id>
python3 ${HERMES_SKILL_DIR}/telethon_checklist.py append --chat -100... --message-id <id> --task "<c>" [--dry-run]
python3 ${HERMES_SKILL_DIR}/telethon_checklist.py toggle --chat -100... --message-id <id> --done <task_id> [--undone <task_id>] [--dry-run]
--chat defaults to me (Saved Messages); for a forum topic add --thread <topic_id> (topic ids come from list-topics; a very large forum lists only the first 100 topics and says so in warnings; a per-topic allowlisted chat lists only its allowed topics)plan and create --dry-run are fully OFFLINE: the Telethon client is never constructed, so "nothing was sent" is guaranteed. --dry-run on append/toggle reads the live list but sends no write--task; get returns tasks with their id and done state; take message_id and task ids from get or from the create outputverified (title, counts, per-task status, sharing flags). Treat verified as the ground truth, not your intention; non-fatal issues arrive under warningscreate --title/--task as given; do not research anything elseplan.json. No draft, partial, or trial lists in the target chat: one final createSplit the work into two phases and write only once.
Phase 1 - research (read-only):
list-topics; if the user says "the whole chat", do not stop at one topic; if the user names a specific topic, use exactly that onePhase 2 - validate via plan.json, then create once: For every candidate keep a source card and admit the task only if it follows directly from what the source actually says:
source:
chat: <id>
topic: <title and id>
message_id: <id>
link: https://t.me/c/<internal_chat_id>/<topic_id>/<message_id>
media: text|photo|document|video|audio|voice|webpage
what_the_source_actually_says: <1-2 precise sentences>
why_it_is_relevant: <the user outcome>
candidate_task: <short task>
The link must be evidence, not decoration: whoever opens it should see where the wording came from. Then assemble plan.json, run plan --file ..., check the source map with your own eyes, show the user a short summary (title, target, N tasks), and only then create --from-plan ....
{
"target": {"chat": -1001234567890, "thread": 33},
"title": "Weekly tasks",
"shared": false,
"collected_from_chat": true,
"tasks": [
{
"text": "Do X: https://t.me/c/1234567890/33/123",
"sources": [
{"link": "https://t.me/c/1234567890/33/123", "topic": "Ideas (33)",
"message_id": 123, "media": "text", "says": "what the source actually says"}
]
}
]
}
collected_from_chat: true turns on script-side enforcement: every task must carry sources with at least one direct https://t.me/... link, and that link must appear inside the task text as a complete URL (a prefix of a longer URL does not count)shared and collected_from_chat must be real JSON booleans (true/false), not strings - the script refuses anything else so a typo can never silently enable shared accessshared: true lets participants append and complete; set it only on an explicit requestFor any potentially useful message with an attachment, analyze the content with the agent's own tools before turning it into a task: documents -> extract the text; photos and screenshots -> read them with vision; video -> duration, key frames, speech via STT when available; voice notes -> STT; links -> open the actual source (a link in a post is a pointer, not proof). Never build a task from a caption or filename guess. If an attachment cannot be read, skip it or tell the user explicitly what remained unverified.
Every checklist item must be: plain language, result-oriented (an outcome, not a pile of technical details), self-contained (understandable without the neighboring items), within Telegram limits (30 tasks, title 255, task 200 - measured in UTF-16 code units, emoji count as 2), free of secrets and personal data, and - when built from a chat - carrying at least one direct source link in the text.
Runtime fallback for large sourced lists: appConfig may advertise 30 items, yet messages.sendMedia can still reject a 30-item checklist with TODO_ITEMS_TOO_MUCH when the items are long and source-linked. A failed request with no message_id created nothing. Rebuild the plan as batches of at most 20 items and shorten each item to about 130 UTF-16 units; validate/dry-run again, create the first batch as a live test, then create the rest only after the first is verified. Do not retry the same rejected 30-item payload.
Bad: Handle the integration thing from the chat
Good: Compare the three CRM offers from the pricing thread and pick one: https://t.me/c/123/45/678
Merge candidates by MEANING, not wording: if two candidates produce the same user outcome, they are one task - keep all supporting links on it. Do not merge genuinely different outcomes. The script rejects normalized-identical texts (case, Unicode compatibility forms, invisible format characters, extra whitespace); everything beyond that - true semantic merging - is your job:
For each item: what user outcome does it deliver?
Is there another item with the same outcome?
If yes - merge them and keep every supporting link
Mark an item done only with verifiable evidence: a command result, the id of a created object, a passing health check. Never because "it was mentioned in the chat", a similar tool exists, or you assume it is done. Keep the evidence (what was checked and how) to report to the user.
Native Telegram To-Do items are NOT text-editable after creation. Therefore finish research, dedup, and link-checking BEFORE the first create. If the user asks to rebuild: get the old list -> carry over the unfinished items (done: false) plus any new ones -> create ONE new list after the full rework -> optionally toggle --done the migrated items in the old list. Delete an old list only on an explicit user command ("replace", "rebuild", "delete the old one") - the script cannot delete, the user does that by hand. Do not post a new version for every cosmetic correction. After a replacement, report the new message_id and the fate of the old list.
--others-append / --others-complete, or shared: true in a plan) only when the user explicitly asks for collaborationAfter an action, reply briefly; do not duplicate the list as text (Telegram already renders it interactively). On failure, say honestly what did not work and why.
verified block matches the intent (title, task count, others_* flags)plan and create --dry-run sent nothing at allok: false and was reported honestly; warnings were read and relayed when relevant