From factory-send
Sends text messages, images, or voice notes to users via Telegram or Discord factory bots. Handles argument parsing and resolves missing platform/type/content interactively.
How this skill is triggered — by the user, by Claude, or both
Slash command
/factory-send:send [telegram|discord] [message|image|voice] [content or path][telegram|discord] [message|image|voice] [content or path]This skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Send content proactively to a user via a running factory bot — without waiting for them
Send content proactively to a user via a running factory bot — without waiting for them to speak first.
Supports: text message, image (file or URL), voice/audio (file).
/send → guided mode (asks everything)
/send telegram message "Deploy done ✅"
/send telegram image /tmp/diagram.png "Here's the diagram"
/send discord message "Reminder: standup in 5 min"
/send telegram voice /tmp/note.ogg
Parse $ARGUMENTS if present:
telegram or discordmessage, image, or voiceMissing pieces → use DP protocol (load ${CLAUDE_PLUGIN_ROOT}/../shared/references/decision-presentation.md):
| Missing | Pattern | Prompt |
|---|---|---|
| platform | DP(A) | "Which platform?" — Telegram · Discord |
| type | DP(A) | "What to send?" — Message · Image · Voice |
| content | DP(B) | "What's the content / file path?" (plain input) |
Telegram — chat_id: The chat_id is the numeric ID of the conversation. Find it in the factory turn history:
python3 - <<'EOF'
from pathlib import Path
import sqlite3, json
factory_dir = Path.home() / '.roxabi' / 'factory'
conn = sqlite3.connect(factory_dir / 'turns.db')
# Show recent unique telegram chat IDs with last message preview
rows = conn.execute("""
SELECT platform_meta, content, created_at
FROM turns
WHERE platform = 'telegram' AND role = 'user'
ORDER BY created_at DESC
LIMIT 20
""").fetchall()
seen = set()
for meta_raw, content, ts in rows:
try:
meta = json.loads(meta_raw) if meta_raw else {}
chat_id = meta.get('chat_id')
if chat_id and chat_id not in seen:
seen.add(chat_id)
print(f"chat_id={chat_id} ({ts[:16]}) \"{content[:60]}\"")
except Exception:
pass
EOF
Discord — channel_id or thread_id:
python3 - <<'EOF'
from pathlib import Path
import sqlite3, json
factory_dir = Path.home() / '.roxabi' / 'factory'
conn = sqlite3.connect(factory_dir / 'turns.db')
rows = conn.execute("""
SELECT platform_meta, content, created_at
FROM turns
WHERE platform = 'discord' AND role = 'user'
ORDER BY created_at DESC
LIMIT 20
""").fetchall()
seen = set()
for meta_raw, content, ts in rows:
try:
meta = json.loads(meta_raw) if meta_raw else {}
cid = meta.get('thread_id') or meta.get('channel_id')
if cid and cid not in seen:
seen.add(cid)
print(f"channel/thread_id={cid} ({ts[:16]}) \"{content[:60]}\"")
except Exception:
pass
EOF
Show the results to the user and ask which ID to use via DP(A). If only one result → use it directly without asking.
Store as TARGET_ID.
Decrypt the bot token and call the API in a single script. The token is never printed or stored outside the script's local variable.
python3 - <<'EOF'
from pathlib import Path
from cryptography.fernet import Fernet
import sqlite3, requests
factory_dir = Path.home() / '.roxabi' / 'factory'
key = (factory_dir / 'keyring.key').read_bytes()
f = Fernet(key)
conn = sqlite3.connect(factory_dir / 'config.db')
row = conn.execute(
'SELECT token FROM bot_secrets WHERE bot_id=? AND platform=?',
('lyra', 'telegram')
).fetchone()
if not row:
print("ERROR: no token found for lyra/telegram")
raise SystemExit(1)
token = f.decrypt(row[0].encode()).decode()
r = requests.post(
f"https://api.telegram.org/bot{token}/sendMessage",
json={"chat_id": TARGET_ID, "text": "CONTENT", "parse_mode": "Markdown"}
)
print(r.status_code, r.json().get('ok'), r.json().get('description', ''))
EOF
python3 - <<'EOF'
from pathlib import Path
from cryptography.fernet import Fernet
import sqlite3, requests
factory_dir = Path.home() / '.roxabi' / 'factory'
key = (factory_dir / 'keyring.key').read_bytes()
f = Fernet(key)
conn = sqlite3.connect(factory_dir / 'config.db')
row = conn.execute(
'SELECT token FROM bot_secrets WHERE bot_id=? AND platform=?',
('lyra', 'telegram')
).fetchone()
if not row:
print("ERROR: no token found for lyra/telegram")
raise SystemExit(1)
token = f.decrypt(row[0].encode()).decode()
with open("FILE_PATH", "rb") as fh:
r = requests.post(
f"https://api.telegram.org/bot{token}/sendPhoto",
data={"chat_id": TARGET_ID, "caption": "CAPTION"},
files={"photo": fh}
)
print(r.status_code, r.json().get('ok'), r.json().get('description', ''))
EOF
python3 - <<'EOF'
from pathlib import Path
from cryptography.fernet import Fernet
import sqlite3, requests
factory_dir = Path.home() / '.roxabi' / 'factory'
key = (factory_dir / 'keyring.key').read_bytes()
f = Fernet(key)
conn = sqlite3.connect(factory_dir / 'config.db')
row = conn.execute(
'SELECT token FROM bot_secrets WHERE bot_id=? AND platform=?',
('lyra', 'telegram')
).fetchone()
if not row:
print("ERROR: no token found for lyra/telegram")
raise SystemExit(1)
token = f.decrypt(row[0].encode()).decode()
r = requests.post(
f"https://api.telegram.org/bot{token}/sendPhoto",
json={"chat_id": TARGET_ID, "photo": "IMAGE_URL", "caption": "CAPTION"}
)
print(r.status_code, r.json().get('ok'), r.json().get('description', ''))
EOF
python3 - <<'EOF'
from pathlib import Path
from cryptography.fernet import Fernet
import sqlite3, requests
factory_dir = Path.home() / '.roxabi' / 'factory'
key = (factory_dir / 'keyring.key').read_bytes()
f = Fernet(key)
conn = sqlite3.connect(factory_dir / 'config.db')
row = conn.execute(
'SELECT token FROM bot_secrets WHERE bot_id=? AND platform=?',
('lyra', 'telegram')
).fetchone()
if not row:
print("ERROR: no token found for lyra/telegram")
raise SystemExit(1)
token = f.decrypt(row[0].encode()).decode()
with open("FILE_PATH", "rb") as fh:
r = requests.post(
f"https://api.telegram.org/bot{token}/sendVoice",
data={"chat_id": TARGET_ID},
files={"voice": fh}
)
print(r.status_code, r.json().get('ok'), r.json().get('description', ''))
EOF
python3 - <<'EOF'
from pathlib import Path
from cryptography.fernet import Fernet
import sqlite3, requests
factory_dir = Path.home() / '.roxabi' / 'factory'
key = (factory_dir / 'keyring.key').read_bytes()
f = Fernet(key)
conn = sqlite3.connect(factory_dir / 'config.db')
row = conn.execute(
'SELECT token FROM bot_secrets WHERE bot_id=? AND platform=?',
('lyra', 'discord')
).fetchone()
if not row:
print("ERROR: no token found for lyra/discord")
raise SystemExit(1)
token = f.decrypt(row[0].encode()).decode()
r = requests.post(
f"https://discord.com/api/v10/channels/TARGET_ID/messages",
headers={"Authorization": f"Bot {token}", "Content-Type": "application/json"},
json={"content": "CONTENT"}
)
print(r.status_code, r.json().get('id', r.text[:100]))
EOF
python3 - <<'EOF'
from pathlib import Path
from cryptography.fernet import Fernet
import sqlite3, requests
factory_dir = Path.home() / '.roxabi' / 'factory'
key = (factory_dir / 'keyring.key').read_bytes()
f = Fernet(key)
conn = sqlite3.connect(factory_dir / 'config.db')
row = conn.execute(
'SELECT token FROM bot_secrets WHERE bot_id=? AND platform=?',
('lyra', 'discord')
).fetchone()
if not row:
print("ERROR: no token found for lyra/discord")
raise SystemExit(1)
token = f.decrypt(row[0].encode()).decode()
with open("FILE_PATH", "rb") as fh:
r = requests.post(
f"https://discord.com/api/v10/channels/TARGET_ID/messages",
headers={"Authorization": f"Bot {token}"},
data={"content": "CAPTION"},
files={"file": ("image.png", fh, "image/png")}
)
print(r.status_code, r.json().get('id', r.text[:100]))
EOF
If output starts with ERROR → stop and tell the user: "No factory bot token found for
{platform}. Make sure factory agent init has been run."
If ok: True (Telegram) or status 200 (Discord) → "✅ Sent successfully."
Otherwise → show the error message and suggest checking:
factory agent init --force)$ARGUMENTS
Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub roxabi/roxabi-factory