Skill: jolestar/telegram-openapi-skill (version 1.0.1)

--- name: telegram-openapi-skill description: Operate Telegram Bot API through UXC with a curated OpenAPI schema, bot-token path auth, polling-based reads, and webhook management guardrails. --- # Telegram Bot API Skill Use this skill to run Telegram Bot API operations through `uxc` + OpenAPI. Reuse the `uxc` skill for shared execution, auth, and error-handling guidance. ## Prerequisites - `uxc` is installed and available in `PATH`. - Network access to `https://api.telegram.org`. - Access to the curated OpenAPI schema URL: - `https://raw.githubusercontent.com/holon-run/uxc/main/skills/telegram-openapi-skill/references/telegram-bot.openapi.json` - A Telegram bot token from BotFather. ## Scope This skill covers a lean bot core surface: - bot identity and chat lookup - text sends - media sends by `file_id`, HTTP URL, or local multipart upload - polling via `getUpdates` - webhook setup/status/delete operations This skill does **not** cover: - multipart media groups with `attach://` file arrays - generic webhook ingestion/runtime hosting - the full Telegram Bot API surface ## Authentication Telegram Bot API requires the bot token in the request path: `https://api.telegram.org/bot<TOKEN>/METHOD_NAME`. Configure the credential with a request path prefix template: ```bash uxc auth credential set telegram-bot \ --auth-type api_key \ --secret-env TELEGRAM_BOT_TOKEN \ --path-prefix-template "/bot{{secret}}" uxc auth binding add \ --id telegram-bot \ --host api.telegram.org \ --scheme https \ --credential telegram-bot \ --priority 100 ``` Validate the local mapping when auth looks wrong: ```bash uxc auth binding match https://api.telegram.org/getMe ``` ## Core Workflow 1. Use the fixed link command by default: - `command -v telegram-openapi-cli` - If missing, create it: `uxc link telegram-openapi-cli https://api.telegram.org --schema-url https://raw.githubusercontent.com/holon-run/uxc/main/skills/telegram-openapi-skill/references/telegram-bot.openapi.json` - `telegram-openapi-cli -h` 2. Inspect operation schema first: - `telegram-openapi-cli get:/getMe -h` - `telegram-openapi-cli post:/sendMessage -h` - `telegram-openapi-cli post:/sendPhoto -h` - `telegram-openapi-cli post:/sendDocument -h` - `telegram-openapi-cli post:/getUpdates -h` 3. Prefer read/setup validation before writes: - `telegram-openapi-cli get:/getMe` - `telegram-openapi-cli get:/getWebhookInfo` - `telegram-openapi-cli get:/getChat chat_id=@channel_or_chat_id` 4. Execute operations with key/value or positional JSON: - key/value: `telegram-openapi-cli post:/sendMessage chat_id=CHAT_ID text="Hello from uxc"` - multipart upload: `telegram-openapi-cli post:/sendPhoto chat_id=CHAT_ID photo=/tmp/photo.jpg caption="Uploaded by uxc"` - positional JSON: `telegram-openapi-cli post:/sendMessage '{"chat_id":"CHAT_ID","text":"Hello from uxc"}'` - daemon-backed polling subscribe: `uxc subscribe start https://api.telegram.org post:/getUpdates '{"timeout":5,"allowed_updates":["message","callback_query"]}' --mode poll --poll-config '{"interval_secs":2,"extract_items_pointer":"/result","request_cursor_arg":"offset","cursor_from_item_pointer":"/update_id","cursor_transform":"increment","checkpoint_strategy":{"type":"item_key","item_key_pointer":"/update_id"}}' --sink file:/tmp/telegram-updates.ndjson` ## Runtime Validation The following Telegram polling flow has been validated against the real Bot API through `uxc`: - `get:/getMe` - `get:/getWebhookInfo` - daemon-backed `uxc subscribe --mode poll` on `post:/getUpdates` - item-derived offset progression from `update_id + 1` - dedupe/checkpoint behavior for repeated polls Observed runtime behavior: - `data` events are emitted for real Telegram updates - `poll` events record fetched/emitted/skipped counts - `checkpoint` events are emitted after new updates are seen - repeated polls skip already-consumed updates after checkpoint advancement ## Operation Groups ### Read / Lookup - `get:/getMe` - `get:/getChat` - `get:/getChatMember` - `get:/getWebhookInfo` ### Messaging - `post:/sendMessage` - `post:/sendPhoto` - `post:/sendDocument` - `post:/sendMediaGroup` ### Update Delivery - `post:/getUpdates` - `post:/setWebhook` - `post:/deleteWebhook` ## Guardrails - Keep automation on the JSON output envelope; do not use `--text`. - Parse stable fields first: `ok`, `kind`, `protocol`, `data`, `error`. - `getUpdates` and webhook delivery are mutually exclusive: - if a webhook is configured, call `post:/deleteWebhook` before polling with `post:/getUpdates` - if polling is active, do not treat webhook operations as background subscription support - Telegram allows only one active `getUpdates` consumer per bot token: - if another bot process or script is polling at the same time, Telegram returns HTTP 409 - stop the other consumer before relying on daemon-backed polling subscribe - For daemon-backed polling subscribe, prefer item-derived offset progression: - `extract_items_pointer` should be `/result` - `request_cursor_arg` should be `offset` - `cursor_from_item_pointer` should be `/update_id` - `cursor_transform` should be `increment` - `checkpoint_strategy.type` should usually be `item_key` with `item_key_pointer=/update_id` - `uxc auth binding match` should be checked against a concrete Telegram method URL such as `https://api.telegram.org/getMe`, because auth is applied through a path-prefix template that expands to `/bot<TOKEN>/...`. - `sendPhoto`, `sendDocument`, and `sendMediaGroup` in this skill accept existing `file_id` values or HTTP URLs only; they do not upload new local files. - `sendPhoto` and `sendDocument` also support `multipart/form-data` local file uploads. File fields must be local path strings. - `sendMediaGroup` still stays JSON-only in this skill because current multipart v1 does not model the `media` array plus `attach://` file set cleanly. - `setWebhook` supports multipart certificate upload for self-signed certs through the `certificate` file field. - Treat `post:/sendMessage`, all `send*` operations, and webhook-changing operations as write/high-risk actions; require explicit user confirmation before execution. - `telegram-openapi-cli <operation> ...` is equivalent to `uxc https://api.telegram.org --schema-url <telegram_openapi_schema> <operation> ...`. ## References - Usage patterns: `references/usage-patterns.md` - Curated OpenAPI schema: `references/telegram-bot.openapi.json` - Telegram Bot API docs: https://core.telegram.org/bots/api - Local Bot API server: https://github.com/tdlib/telegram-bot-api

path: agents/openai.yaml
content: interface:
  display_name: "Telegram Bot API"
  short_description: "Operate Telegram Bot API via UXC + curated OpenAPI schema"
  default_prompt: "Use $telegram-openapi-skill to discover and execute Telegram Bot API operations through UXC with bot-token path auth and polling/webhook guardrails."


path: references/telegram-bot.openapi.json
content: {
  "openapi": "3.0.3",
  "info": {
    "title": "Telegram Bot API (Curated Lean Core)",
    "version": "1.0.0"
  },
  "servers": [
    {
      "url": "https://api.telegram.org"
    }
  ],
  "paths": {
    "/getMe": {
      "get": {
        "summary": "Get basic bot identity information",
        "responses": {
          "200": {
            "description": "Bot identity response"
          }
        }
      }
    },
    "/getChat": {
      "get": {
        "summary": "Get information about a chat the bot can access",
        "parameters": [
          {
            "name": "chat_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Unique identifier for the target chat or username of the target supergroup/channel"
          }
        ],
        "responses": {
          "200": {
            "description": "Chat info response"
          }
        }
      }
    },
    "/getChatMember": {
      "get": {
        "summary": "Get information about a member of a chat",
        "parameters": [
          {
            "name": "chat_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "user_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Chat member response"
          }
        }
      }
    },
    "/getWebhookInfo": {
      "get": {
        "summary": "Get current webhook status",
        "responses": {
          "200": {
            "description": "Webhook info response"
          }
        }
      }
    },
    "/sendMessage": {
      "post": {
        "summary": "Send a text message",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SendMessageRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sent message response"
          }
        }
      }
    },
    "/sendPhoto": {
      "post": {
        "summary": "Send a photo by file_id, HTTP URL, or local multipart upload",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SendPhotoRequest"
              }
            },
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/SendPhotoMultipartRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sent photo response"
          }
        }
      }
    },
    "/sendDocument": {
      "post": {
        "summary": "Send a document by file_id, HTTP URL, or local multipart upload",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SendDocumentRequest"
              }
            },
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/SendDocumentMultipartRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sent document response"
          }
        }
      }
    },
    "/sendMediaGroup": {
      "post": {
        "summary": "Send a group of photos or documents by file_id or HTTP URL",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SendMediaGroupRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sent media group response"
          }
        }
      }
    },
    "/getUpdates": {
      "post": {
        "summary": "Poll for incoming updates",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GetUpdatesRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updates response"
          }
        }
      }
    },
    "/setWebhook": {
      "post": {
        "summary": "Configure webhook delivery, optionally with multipart certificate upload",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetWebhookRequest"
              }
            },
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/SetWebhookMultipartRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Webhook set response"
          }
        }
      }
    },
    "/deleteWebhook": {
      "post": {
        "summary": "Delete webhook configuration",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DeleteWebhookRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Webhook delete response"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "ChatId": {
        "oneOf": [
          {
            "type": "string"
          },
          {
            "type": "integer",
            "format": "int64"
          }
        ]
      },
      "ReplyMarkup": {
        "type": "object",
        "additionalProperties": true,
        "description": "Telegram reply markup object passed as JSON"
      },
      "SendMessageRequest": {
        "type": "object",
        "required": [
          "chat_id",
          "text"
        ],
        "properties": {
          "chat_id": {
            "$ref": "#/components/schemas/ChatId"
          },
          "text": {
            "type": "string"
          },
          "parse_mode": {
            "type": "string"
          },
          "disable_notification": {
            "type": "boolean"
          },
          "protect_content": {
            "type": "boolean"
          },
          "reply_markup": {
            "$ref": "#/components/schemas/ReplyMarkup"
          }
        },
        "additionalProperties": true
      },
      "SendPhotoRequest": {
        "type": "object",
        "required": [
          "chat_id",
          "photo"
        ],
        "properties": {
          "chat_id": {
            "$ref": "#/components/schemas/ChatId"
          },
          "photo": {
            "type": "string",
            "description": "Existing file_id or HTTP URL only in this curated schema"
          },
          "caption": {
            "type": "string"
          },
          "parse_mode": {
            "type": "string"
          },
          "disable_notification": {
            "type": "boolean"
          },
          "protect_content": {
            "type": "boolean"
          },
          "reply_markup": {
            "$ref": "#/components/schemas/ReplyMarkup"
          }
        },
        "additionalProperties": true
      },
      "SendPhotoMultipartRequest": {
        "type": "object",
        "required": [
          "chat_id",
          "photo"
        ],
        "properties": {
          "chat_id": {
            "$ref": "#/components/schemas/ChatId"
          },
          "photo": {
            "type": "string",
            "format": "binary",
            "description": "Local file path to upload as the photo body"
          },
          "caption": {
            "type": "string"
          },
          "parse_mode": {
            "type": "string"
          },
          "disable_notification": {
            "type": "boolean"
          },
          "protect_content": {
            "type": "boolean"
          }
        },
        "additionalProperties": false
      },
      "SendDocumentRequest": {
        "type": "object",
        "required": [
          "chat_id",
          "document"
        ],
        "properties": {
          "chat_id": {
            "$ref": "#/components/schemas/ChatId"
          },
          "document": {
            "type": "string",
            "description": "Existing file_id or HTTP URL only in this curated schema"
          },
          "caption": {
            "type": "string"
          },
          "parse_mode": {
            "type": "string"
          },
          "disable_notification": {
            "type": "boolean"
          },
          "protect_content": {
            "type": "boolean"
          },
          "reply_markup": {
            "$ref": "#/components/schemas/ReplyMarkup"
          }
        },
        "additionalProperties": true
      },
      "SendDocumentMultipartRequest": {
        "type": "object",
        "required": [
          "chat_id",
          "document"
        ],
        "properties": {
          "chat_id": {
            "$ref": "#/components/schemas/ChatId"
          },
          "document": {
            "type": "string",
            "format": "binary",
            "description": "Local file path to upload as the document body"
          },
          "caption": {
            "type": "string"
          },
          "parse_mode": {
            "type": "string"
          },
          "disable_notification": {
            "type": "boolean"
          },
          "protect_content": {
            "type": "boolean"
          }
        },
        "additionalProperties": false
      },
      "InputMediaItem": {
        "type": "object",
        "required": [
          "type",
          "media"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "photo",
              "document"
            ]
          },
          "media": {
            "type": "string",
            "description": "Existing file_id or HTTP URL only in this curated schema"
          },
          "caption": {
            "type": "string"
          },
          "parse_mode": {
            "type": "string"
          }
        },
        "additionalProperties": true
      },
      "SendMediaGroupRequest": {
        "type": "object",
        "required": [
          "chat_id",
          "media"
        ],
        "properties": {
          "chat_id": {
            "$ref": "#/components/schemas/ChatId"
          },
          "media": {
            "type": "array",
            "minItems": 2,
            "items": {
              "$ref": "#/components/schemas/InputMediaItem"
            }
          },
          "disable_notification": {
            "type": "boolean"
          },
          "protect_content": {
            "type": "boolean"
          },
          "reply_to_message_id": {
            "type": "integer",
            "format": "int64"
          }
        },
        "additionalProperties": true
      },
      "GetUpdatesRequest": {
        "type": "object",
        "properties": {
          "offset": {
            "type": "integer",
            "format": "int64"
          },
          "limit": {
            "type": "integer"
          },
          "timeout": {
            "type": "integer"
          },
          "allowed_updates": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "additionalProperties": true
      },
      "SetWebhookRequest": {
        "type": "object",
        "required": [
          "url"
        ],
        "properties": {
          "url": {
            "type": "string"
          },
          "ip_address": {
            "type": "string"
          },
          "max_connections": {
            "type": "integer"
          },
          "allowed_updates": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "drop_pending_updates": {
            "type": "boolean"
          },
          "secret_token": {
            "type": "string"
          }
        },
        "additionalProperties": true
      },
      "SetWebhookMultipartRequest": {
        "type": "object",
        "required": [
          "url"
        ],
        "properties": {
          "url": {
            "type": "string"
          },
          "certificate": {
            "type": "string",
            "format": "binary",
            "description": "Local PEM certificate file path for self-signed webhook setups"
          },
          "ip_address": {
            "type": "string"
          },
          "max_connections": {
            "type": "integer"
          },
          "drop_pending_updates": {
            "type": "boolean"
          },
          "secret_token": {
            "type": "string"
          }
        },
        "additionalProperties": false
      },
      "DeleteWebhookRequest": {
        "type": "object",
        "properties": {
          "drop_pending_updates": {
            "type": "boolean"
          }
        },
        "additionalProperties": true
      }
    }
  }
}


path: references/usage-patterns.md
content: # Telegram Bot API Skill - Usage Patterns

## Link Setup

```bash
command -v telegram-openapi-cli
uxc link telegram-openapi-cli https://api.telegram.org \
  --schema-url https://raw.githubusercontent.com/holon-run/uxc/main/skills/telegram-openapi-skill/references/telegram-bot.openapi.json
telegram-openapi-cli -h
```

## Auth Setup

```bash
uxc auth credential set telegram-bot \
  --auth-type api_key \
  --secret-env TELEGRAM_BOT_TOKEN \
  --path-prefix-template "/bot{{secret}}"

uxc auth binding add \
  --id telegram-bot \
  --host api.telegram.org \
  --scheme https \
  --credential telegram-bot \
  --priority 100

uxc auth binding match https://api.telegram.org/getMe
```

## Read Examples

```bash
# Confirm the bot identity
telegram-openapi-cli get:/getMe

# Inspect a chat the bot can access
telegram-openapi-cli get:/getChat chat_id=@my_channel

# Check webhook state before deciding between webhook and polling
telegram-openapi-cli get:/getWebhookInfo
```

## Polling Example

```bash
# Remove webhook first if one is configured
telegram-openapi-cli post:/deleteWebhook '{"drop_pending_updates":false}'

# Poll for updates with long-poll timeout
telegram-openapi-cli post:/getUpdates '{"timeout":30,"allowed_updates":["message","callback_query"]}'

# Run background polling through uxc subscribe with offset derived from update_id + 1
# Only one getUpdates consumer can be active for the bot token at a time.
uxc subscribe start https://api.telegram.org post:/getUpdates \
  '{"timeout":5,"allowed_updates":["message","callback_query"]}' \
  --mode poll \
  --poll-config '{"interval_secs":2,"extract_items_pointer":"/result","request_cursor_arg":"offset","cursor_from_item_pointer":"/update_id","cursor_transform":"increment","checkpoint_strategy":{"type":"item_key","item_key_pointer":"/update_id"}}' \
  --sink file:/tmp/telegram-updates.ndjson
```

## Write Examples (Confirm Intent First)

```bash
# Send a text message
telegram-openapi-cli post:/sendMessage '{"chat_id":"CHAT_ID","text":"Hello from UXC"}'

# Send a photo by existing file_id or HTTP URL
telegram-openapi-cli post:/sendPhoto '{"chat_id":"CHAT_ID","photo":"https://example.com/photo.jpg","caption":"From UXC"}'

# Send a photo by local multipart upload
telegram-openapi-cli post:/sendPhoto chat_id=CHAT_ID photo=/tmp/photo.jpg caption="Uploaded by UXC"

# Send a document by local multipart upload
telegram-openapi-cli post:/sendDocument chat_id=CHAT_ID document=/tmp/report.pdf caption="Report"

# Configure a webhook with a self-signed certificate file
telegram-openapi-cli post:/setWebhook url=https://example.com/telegram-webhook certificate=/tmp/public.pem secret_token=secret123
```

## Fallback Equivalence

- `telegram-openapi-cli <operation> ...` is equivalent to
  `uxc https://api.telegram.org --schema-url <telegram_openapi_schema> <operation> ...`.


path: scripts/validate.sh
content: #!/usr/bin/env bash
set -euo pipefail

ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
SKILL_DIR="${ROOT_DIR}/skills/telegram-openapi-skill"
SKILL_FILE="${SKILL_DIR}/SKILL.md"
OPENAI_FILE="${SKILL_DIR}/agents/openai.yaml"
USAGE_FILE="${SKILL_DIR}/references/usage-patterns.md"
SCHEMA_FILE="${SKILL_DIR}/references/telegram-bot.openapi.json"

fail() {
  printf '[validate] error: %s\n' "$*" >&2
  exit 1
}

need_cmd() {
  command -v "$1" >/dev/null 2>&1 || fail "required command not found: $1"
}

need_cmd rg

for file in "${SKILL_FILE}" "${OPENAI_FILE}" "${USAGE_FILE}" "${SCHEMA_FILE}"; do
  [[ -f "${file}" ]] || fail "missing required file: ${file}"
done

rg -q '^name:\s*telegram-openapi-skill\s*$' "${SKILL_FILE}" || fail 'invalid skill name'
rg -q '^description:\s*.+' "${SKILL_FILE}" || fail 'missing description'

rg -q 'command -v telegram-openapi-cli' "${SKILL_FILE}" || fail 'missing link-first command check'
rg -q 'uxc link telegram-openapi-cli https://api.telegram.org --schema-url ' "${SKILL_FILE}" || fail 'missing fixed link create command with schema-url'
rg -q 'telegram-openapi-cli -h' "${SKILL_FILE}" || fail 'missing help-first host discovery example'
rg -q 'telegram-openapi-cli get:/getMe -h' "${SKILL_FILE}" || fail 'missing operation-level help example'
rg -q 'telegram-openapi-cli post:/sendPhoto -h' "${SKILL_FILE}" || fail 'missing multipart photo help example'
rg -q 'telegram-openapi-cli post:/sendDocument -h' "${SKILL_FILE}" || fail 'missing multipart document help example'
rg -q -- '--path-prefix-template "/bot\{\{secret\}\}"' "${SKILL_FILE}" || fail 'missing Telegram path auth setup'
rg -q 'uxc auth binding match https://api.telegram.org' "${SKILL_FILE}" || fail 'missing binding match check'
rg -q 'multipart/form-data' "${SKILL_FILE}" || fail 'missing multipart guidance'
rg -q 'positional JSON' "${SKILL_FILE}" || fail 'missing positional JSON guidance'

if rg -q -- "--args\\s+'\\{" "${SKILL_FILE}" "${USAGE_FILE}"; then
  fail 'found banned legacy JSON argument pattern'
fi

rg -q '^\s*display_name:\s*"Telegram Bot API"\s*$' "${OPENAI_FILE}" || fail 'missing display_name'
rg -q '^\s*short_description:\s*".+"\s*$' "${OPENAI_FILE}" || fail 'missing short_description'
rg -q '^\s*default_prompt:\s*".*\$telegram-openapi-skill.*"\s*$' "${OPENAI_FILE}" || fail 'default_prompt must mention $telegram-openapi-skill'

echo "skills/telegram-openapi-skill validation passed"


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)