Building the iujinwee Telegram Plugin

The claude-plugins-official:telegram plugin bridges Telegram and Claude Code β€” messages from your Telegram bot land directly in the Claude session as conversation turns. Quite a useful foundation, but the default plugin only handles message delivery. I’ve been extending it with project-aware features, the first of which is /command: a skill for discovering and managing project-scoped Claude skills without leaving the chat.

Problem Statement

When you’re working across multiple projects, each with their own .claude/skills/ directory, there’s no easy way to know what skills are available β€” let alone run them β€” from Telegram. You’d have to open the terminal, navigate to the project, and manually invoke /skill-name. The /command feature fixes this by exposing the full skill catalog through the Telegram interface, with runtime argument collection and interactive management (create, edit, delete).

The /command Feature

/command dispatches based on the first token:

  • /command or /command list β€” lists all skills grouped by source (project-local vs global)
  • /command <name> [args...] β€” runs the named skill, prompting for any required inputs
  • /command create <name> β€” creates a new skill interactively
  • /command edit <name> β€” edits an existing skill
  • /command rm <name> β€” deletes a skill after confirmation

Skills are discovered from .claude/skills/ (project-local) and ~/.claude/skills/ (global fallback), giving project-scoped context without any configuration.

One thing I had to be careful about: the skill explicitly blocks execution from Telegram channel messages. A Telegram message asking Claude to run /command rm production-deploy would be prompt injection. The skill validates that execution is terminal-initiated only.

Developing Plugins

The Claude Code plugin system is more capable than I initially expected β€” it’s not just skills. There are several component types, each serving a different integration point.

Skills

The bread and butter. A skill is a directory with a SKILL.md file that contains YAML frontmatter and natural-language instructions. Claude treats the instructions as its own when the skill is invoked. Skills support structured inputs (required/optional), runtime argument substitution, and can bundle supporting scripts.

skills/
β”œβ”€β”€ command/
β”‚   └── SKILL.md
└── deploy/
    β”œβ”€β”€ SKILL.md
    └── scripts/
        └── deploy.sh

Skills are auto-discovered at session start and can be invoked manually (/skill-name) or automatically by Claude based on task context.

Agents

Specialized subagents that Claude spawns for specific tasks. Defined as markdown files with frontmatter controlling model, effort level, max turns, and tool restrictions. Appear in the /agents interface and can be invoked automatically or manually.

---
name: security-reviewer
description: Audits code changes for security issues
model: sonnet
effort: high
maxTurns: 20
disallowedTools: Write, Edit
---

Worth noting: plugin agents can’t configure hooks, MCP servers, or permission modes β€” that’s a deliberate security boundary.

Hooks

Event handlers that fire automatically when Claude Code does something. The hooks I already use in the vault (PostToolUse on Write to enforce blog indexes) are the same mechanism plugins use. The full event list is much broader than I realised:

EventWhen
SessionStart / SessionEndSession lifecycle
PreToolUse / PostToolUseAround any tool call (can block)
UserPromptSubmitBefore Claude processes input
StopWhen Claude finishes responding
SubagentStart / SubagentStopAround subagent invocations
FileChangedWhen a watched file changes on disk
PreCompact / PostCompactAround context compaction

Hook types go beyond shell commands too β€” you can fire an HTTP request, call an MCP tool, evaluate a prompt with an LLM, or even run a full agentic verifier.

MCP Servers

Model Context Protocol servers that expose tools Claude can call. The telegram plugin itself uses this β€” the Telegram bot is an MCP server that receives messages and exposes reply, react, and edit_message tools.

Plugin MCP servers start automatically when the plugin is active, and the ${CLAUDE_PLUGIN_ROOT} variable makes it easy to reference bundled server binaries without hardcoded paths.

LSP Servers

Language Server Protocol integration β€” gives Claude real-time code intelligence (diagnostics, go-to-definition, hover types) while editing files. The plugin configures how Claude connects to the language server; you install the binary separately.

{
  "go": {
    "command": "gopls",
    "args": ["serve"],
    "extensionToLanguage": { ".go": "go" }
  }
}

Less relevant for the telegram plugin specifically, but extremely useful for any plugin that touches code.

Monitors

Background processes that run for the lifetime of a session and pipe every stdout line to Claude as a notification. The telegram plugin uses one to watch for incoming messages β€” a long-running process polls the bot and delivers messages without Claude having to poll.

[
  {
    "name": "telegram-listener",
    "command": "\"${CLAUDE_PLUGIN_ROOT}\"/scripts/listen.sh",
    "description": "Incoming Telegram messages"
  }
]

The when field can defer startup until a specific skill is invoked β€” handy for expensive monitors you don’t want running on every session.

Themes

JSON files that ship color themes alongside the plugin. They appear in /theme next to the built-ins. A theme specifies a base preset (dark or light) and a sparse overrides map of color tokens. Currently experimental, but kinda cool as a bundling mechanism β€” a plugin could ship its own visual identity.

{
  "name": "Dracula",
  "base": "dark",
  "overrides": {
    "claude": "#bd93f9",
    "error": "#ff5555"
  }
}

Channels

The piece that makes the telegram plugin possible. The channels field in plugin.json declares message channels that inject content directly into the Claude conversation. Each channel binds to an MCP server and can declare its own userConfig for prompting bot tokens at enable time.

{
  "channels": [
    {
      "server": "telegram",
      "userConfig": {
        "bot_token": {
          "type": "string",
          "title": "Bot token",
          "sensitive": true
        }
      }
    }
  ]
}

This is what turns a Telegram message into a conversation turn β€” the channel declaration wires the MCP server output into Claude’s input stream. Without it, the MCP server would just expose tools, not inject context.

Plugin Structure

The full directory layout for a plugin like this:

iujinwee-telegram/
β”œβ”€β”€ .claude-plugin/
β”‚   └── plugin.json         ← metadata + channel declaration
β”œβ”€β”€ skills/
β”‚   β”œβ”€β”€ command/
β”‚   β”‚   └── SKILL.md        ← /command dispatcher
β”‚   β”œβ”€β”€ access/
β”‚   β”‚   └── SKILL.md        ← allowlist management
β”‚   └── configure/
β”‚       └── SKILL.md        ← bot token setup
β”œβ”€β”€ monitors/
β”‚   └── monitors.json       ← telegram listener process
β”œβ”€β”€ .mcp.json               ← MCP server (the actual bot)
└── scripts/
    └── listen.sh           ← polling script

Technologies

  • Claude Code Plugin API (skills, channels, monitors, MCP)
  • Telegram Bot API via MCP server
  • Shell scripts for the listener process

Progress Tracker

  • Telegram channel delivery (messages appear in Claude session) β€” 2026-05
  • /command skill for skill discovery and management β€” 2026-05-26
  • /command search β€” keyword search across skill descriptions
  • Skill argument autocomplete hints in Telegram replies
  • /schedule integration β€” trigger scheduled agents from Telegram

Thanks for reading β€” if you’re building something similar on top of Claude Code’s plugin system, the official plugin reference is surprisingly complete.