chore: auto-commit from dashboard
This commit is contained in:
@@ -15,6 +15,7 @@ from src.claude_session import (
|
||||
PROJECT_ROOT,
|
||||
VALID_MODELS,
|
||||
)
|
||||
from src.fast_commands import dispatch as fast_dispatch
|
||||
from src.router import route_message
|
||||
|
||||
logger = logging.getLogger("echo-core.discord")
|
||||
@@ -114,26 +115,47 @@ def create_bot(config: Config) -> discord.Client:
|
||||
"**Echo Commands**",
|
||||
"`/ping` — Check bot latency",
|
||||
"`/help` — Show this help message",
|
||||
"`/setup` — Claim ownership of the bot (first run only)",
|
||||
"`/channel add <alias>` — Register current channel (owner only)",
|
||||
"`/channels` — List registered channels",
|
||||
"`/admin add <user_id>` — Add an admin (owner only)",
|
||||
"`/clear` — Clear the session for this channel",
|
||||
"`/status` — Show session status for this channel",
|
||||
"`/model` — View current model and available models",
|
||||
"`/model <choice>` — Change model for this channel's session",
|
||||
"`/logs [n]` — Show last N log lines (default 10)",
|
||||
"`/restart` — Restart the bot process (owner only)",
|
||||
"`/heartbeat` — Run heartbeat health checks",
|
||||
"`/search <query>` — Search Echo's memory",
|
||||
"`/status` — Show session status",
|
||||
"`/model [choice]` — View/change AI model",
|
||||
"",
|
||||
"**Cron Jobs**",
|
||||
"`/cron list` — List all scheduled jobs",
|
||||
"`/cron run <name>` — Force-run a job now",
|
||||
"`/cron add <name> <expr> [model]` — Create a scheduled job (admin)",
|
||||
"`/cron remove <name>` — Remove a job (admin)",
|
||||
"`/cron enable <name>` — Enable a job (admin)",
|
||||
"`/cron disable <name>` — Disable a job (admin)",
|
||||
"**Email**",
|
||||
"`/email check` — Check unread emails",
|
||||
"`/email send <to> <subject> <body>` — Send an email",
|
||||
"`/email save` — Save unread emails to KB",
|
||||
"",
|
||||
"**Calendar**",
|
||||
"`/calendar today` — Today + tomorrow events",
|
||||
"`/calendar week` — This week's schedule",
|
||||
"`/calendar busy` — Am I in a meeting?",
|
||||
"",
|
||||
"**Notes**",
|
||||
"`/note <text>` — Quick note in daily file",
|
||||
"`/jurnal <text>` — Journal entry in daily file",
|
||||
"`/search <query>` — Search Echo's memory",
|
||||
"`/kb [category]` — Recent KB notes",
|
||||
"",
|
||||
"**Reminders**",
|
||||
"`/remind <time> <text> [date]` — Create reminder",
|
||||
"",
|
||||
"**Git**",
|
||||
"`/commit [message]` — Commit all changes",
|
||||
"`/push` — Push to remote",
|
||||
"`/pull` — Pull with rebase",
|
||||
"`/test [pattern]` — Run tests",
|
||||
"",
|
||||
"**Ops**",
|
||||
"`/logs [n]` — Last N log lines",
|
||||
"`/doctor` — System diagnostics",
|
||||
"`/heartbeat` — Health checks",
|
||||
"`/restart` — Restart bot (owner)",
|
||||
"",
|
||||
"**Admin**",
|
||||
"`/setup` — Claim ownership",
|
||||
"`/channel add <alias>` — Register channel",
|
||||
"`/channels` — List channels",
|
||||
"`/admin add <user_id>` — Add admin",
|
||||
"`/cron list|run|add|remove|enable|disable` — Cron jobs",
|
||||
]
|
||||
await interaction.response.send_message(
|
||||
"\n".join(lines), ephemeral=True
|
||||
@@ -409,6 +431,158 @@ def create_bot(config: Config) -> discord.Client:
|
||||
|
||||
tree.add_command(cron_group)
|
||||
|
||||
# --- Email commands ---
|
||||
|
||||
email_group = app_commands.Group(
|
||||
name="email", description="Email operations"
|
||||
)
|
||||
|
||||
@email_group.command(name="check", description="Check unread emails")
|
||||
async def email_check(interaction: discord.Interaction) -> None:
|
||||
await interaction.response.defer()
|
||||
result = await asyncio.to_thread(fast_dispatch, "email", [])
|
||||
await interaction.followup.send(result)
|
||||
|
||||
@email_group.command(name="send", description="Send an email")
|
||||
@app_commands.describe(
|
||||
to="Recipient email address",
|
||||
subject="Email subject",
|
||||
body="Email body text",
|
||||
)
|
||||
async def email_send(
|
||||
interaction: discord.Interaction, to: str, subject: str, body: str
|
||||
) -> None:
|
||||
await interaction.response.defer()
|
||||
result = await asyncio.to_thread(
|
||||
fast_dispatch, "email", ["send", f"{to} {subject} :: {body}"]
|
||||
)
|
||||
await interaction.followup.send(result)
|
||||
|
||||
@email_group.command(name="save", description="Save unread emails to knowledge base")
|
||||
async def email_save(interaction: discord.Interaction) -> None:
|
||||
await interaction.response.defer()
|
||||
result = await asyncio.to_thread(fast_dispatch, "email", ["save"])
|
||||
await interaction.followup.send(result)
|
||||
|
||||
tree.add_command(email_group)
|
||||
|
||||
# --- Calendar commands ---
|
||||
|
||||
calendar_group = app_commands.Group(
|
||||
name="calendar", description="Calendar operations"
|
||||
)
|
||||
|
||||
@calendar_group.command(name="today", description="Today and tomorrow events")
|
||||
async def calendar_today(interaction: discord.Interaction) -> None:
|
||||
await interaction.response.defer()
|
||||
result = await asyncio.to_thread(fast_dispatch, "calendar", [])
|
||||
await interaction.followup.send(result)
|
||||
|
||||
@calendar_group.command(name="week", description="This week's schedule")
|
||||
async def calendar_week(interaction: discord.Interaction) -> None:
|
||||
await interaction.response.defer()
|
||||
result = await asyncio.to_thread(fast_dispatch, "calendar", ["week"])
|
||||
await interaction.followup.send(result)
|
||||
|
||||
@calendar_group.command(name="busy", description="Am I in a meeting right now?")
|
||||
async def calendar_busy(interaction: discord.Interaction) -> None:
|
||||
await interaction.response.defer()
|
||||
result = await asyncio.to_thread(fast_dispatch, "calendar", ["busy"])
|
||||
await interaction.followup.send(result)
|
||||
|
||||
tree.add_command(calendar_group)
|
||||
|
||||
# --- Note & journal commands ---
|
||||
|
||||
@tree.command(name="note", description="Add a quick note to today's daily file")
|
||||
@app_commands.describe(text="Note text")
|
||||
async def note_cmd(interaction: discord.Interaction, text: str) -> None:
|
||||
await interaction.response.defer(ephemeral=True)
|
||||
result = await asyncio.to_thread(fast_dispatch, "note", text.split())
|
||||
await interaction.followup.send(result, ephemeral=True)
|
||||
|
||||
@tree.command(name="jurnal", description="Add a journal entry to today's daily file")
|
||||
@app_commands.describe(text="Journal entry text")
|
||||
async def jurnal_cmd(interaction: discord.Interaction, text: str) -> None:
|
||||
await interaction.response.defer(ephemeral=True)
|
||||
result = await asyncio.to_thread(fast_dispatch, "jurnal", text.split())
|
||||
await interaction.followup.send(result, ephemeral=True)
|
||||
|
||||
# --- KB command ---
|
||||
|
||||
@tree.command(name="kb", description="List recent knowledge base notes")
|
||||
@app_commands.describe(category="Filter by category (optional)")
|
||||
async def kb_cmd(
|
||||
interaction: discord.Interaction, category: str | None = None
|
||||
) -> None:
|
||||
await interaction.response.defer()
|
||||
args = [category] if category else []
|
||||
result = await asyncio.to_thread(fast_dispatch, "kb", args)
|
||||
await interaction.followup.send(result)
|
||||
|
||||
# --- Remind command ---
|
||||
|
||||
@tree.command(name="remind", description="Create a calendar reminder")
|
||||
@app_commands.describe(
|
||||
time="Time in HH:MM format",
|
||||
text="Reminder text",
|
||||
date="Date in YYYY-MM-DD format (default: today)",
|
||||
)
|
||||
async def remind_cmd(
|
||||
interaction: discord.Interaction, time: str, text: str, date: str | None = None
|
||||
) -> None:
|
||||
await interaction.response.defer(ephemeral=True)
|
||||
args = [date, time, text] if date else [time, text]
|
||||
result = await asyncio.to_thread(fast_dispatch, "remind", args)
|
||||
await interaction.followup.send(result, ephemeral=True)
|
||||
|
||||
# --- Git commands ---
|
||||
|
||||
@tree.command(name="commit", description="Git commit all changes")
|
||||
@app_commands.describe(message="Commit message (auto-generated if empty)")
|
||||
async def commit_cmd(
|
||||
interaction: discord.Interaction, message: str | None = None
|
||||
) -> None:
|
||||
await interaction.response.defer()
|
||||
args = message.split() if message else []
|
||||
result = await asyncio.to_thread(fast_dispatch, "commit", args)
|
||||
await interaction.followup.send(result)
|
||||
|
||||
@tree.command(name="push", description="Git push to remote")
|
||||
async def push_cmd(interaction: discord.Interaction) -> None:
|
||||
await interaction.response.defer()
|
||||
result = await asyncio.to_thread(fast_dispatch, "push", [])
|
||||
await interaction.followup.send(result)
|
||||
|
||||
@tree.command(name="pull", description="Git pull with rebase")
|
||||
async def pull_cmd(interaction: discord.Interaction) -> None:
|
||||
await interaction.response.defer()
|
||||
result = await asyncio.to_thread(fast_dispatch, "pull", [])
|
||||
await interaction.followup.send(result)
|
||||
|
||||
# --- Test command ---
|
||||
|
||||
@tree.command(name="test", description="Run pytest tests")
|
||||
@app_commands.describe(pattern="Test filter pattern (optional)")
|
||||
async def test_cmd(
|
||||
interaction: discord.Interaction, pattern: str | None = None
|
||||
) -> None:
|
||||
await interaction.response.defer()
|
||||
args = [pattern] if pattern else []
|
||||
result = await asyncio.to_thread(fast_dispatch, "test", args)
|
||||
# Truncate for Discord limit
|
||||
if len(result) > 1900:
|
||||
result = result[-1900:]
|
||||
await interaction.followup.send(f"```\n{result}\n```")
|
||||
|
||||
# --- Doctor command ---
|
||||
|
||||
@tree.command(name="doctor", description="System diagnostics")
|
||||
async def doctor_cmd(interaction: discord.Interaction) -> None:
|
||||
await interaction.response.defer(ephemeral=True)
|
||||
result = await asyncio.to_thread(fast_dispatch, "doctor", [])
|
||||
await interaction.followup.send(result, ephemeral=True)
|
||||
|
||||
@tree.command(name="heartbeat", description="Run heartbeat health checks")
|
||||
async def heartbeat_cmd(interaction: discord.Interaction) -> None:
|
||||
from src.heartbeat import run_heartbeat
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
|
||||
from telegram import BotCommand, Update, InlineKeyboardButton, InlineKeyboardMarkup
|
||||
from telegram.constants import ChatAction, ChatType
|
||||
from telegram.ext import (
|
||||
Application,
|
||||
@@ -21,6 +21,7 @@ from src.claude_session import (
|
||||
set_session_model,
|
||||
VALID_MODELS,
|
||||
)
|
||||
from src.fast_commands import dispatch as fast_dispatch
|
||||
from src.router import route_message
|
||||
|
||||
logger = logging.getLogger("echo-core.telegram")
|
||||
@@ -106,12 +107,40 @@ async def cmd_help(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||
"""Handle /help — list commands."""
|
||||
lines = [
|
||||
"*Echo Commands*",
|
||||
"/start — Welcome message",
|
||||
"/help — Show this help",
|
||||
"/clear — Clear the session for this chat",
|
||||
"/status — Show session status",
|
||||
"/clear — Clear session",
|
||||
"/status — Session status",
|
||||
"/model — View/change AI model",
|
||||
"/register <alias> — Register this chat (owner only)",
|
||||
"",
|
||||
"*Email*",
|
||||
"/email — Check unread emails",
|
||||
"/emailsend <to> <subject> :: <body>",
|
||||
"/emailsave — Save emails to KB",
|
||||
"",
|
||||
"*Calendar*",
|
||||
"/calendar — Today + tomorrow",
|
||||
"/calendarweek — Week schedule",
|
||||
"/calendarbusy — Am I busy?",
|
||||
"",
|
||||
"*Notes*",
|
||||
"/note <text> — Quick note",
|
||||
"/jurnal <text> — Journal entry",
|
||||
"/search <query> — Memory search",
|
||||
"/kb [category] — KB notes",
|
||||
"",
|
||||
"*Reminders*",
|
||||
"/remind <HH:MM> <text>",
|
||||
"",
|
||||
"*Git*",
|
||||
"/commit [msg] — Commit changes",
|
||||
"/push — Push to remote",
|
||||
"/pull — Pull with rebase",
|
||||
"/test [pattern] — Run tests",
|
||||
"",
|
||||
"*Ops*",
|
||||
"/logs [N] — Log lines",
|
||||
"/doctor — Diagnostics",
|
||||
"/heartbeat — Health checks",
|
||||
]
|
||||
await update.message.reply_text("\n".join(lines), parse_mode="Markdown")
|
||||
|
||||
@@ -270,6 +299,126 @@ async def cmd_register(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No
|
||||
)
|
||||
|
||||
|
||||
# --- Fast command handlers ---
|
||||
|
||||
|
||||
async def _fast_cmd(update: Update, name: str, args: list[str]) -> None:
|
||||
"""Run a fast command and reply with the result."""
|
||||
await update.message.chat.send_action(ChatAction.TYPING)
|
||||
result = await asyncio.to_thread(fast_dispatch, name, args)
|
||||
if result:
|
||||
for chunk in split_message(result):
|
||||
await update.message.reply_text(chunk)
|
||||
else:
|
||||
await update.message.reply_text(f"Unknown command: /{name}")
|
||||
|
||||
|
||||
async def cmd_email(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||
"""/email — check unread emails."""
|
||||
await _fast_cmd(update, "email", list(context.args or []))
|
||||
|
||||
|
||||
async def cmd_emailsend(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||
"""/emailsend <to> <subject> :: <body>"""
|
||||
await _fast_cmd(update, "email", ["send"] + list(context.args or []))
|
||||
|
||||
|
||||
async def cmd_emailsave(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||
"""/emailsave — save unread emails to KB."""
|
||||
await _fast_cmd(update, "email", ["save"])
|
||||
|
||||
|
||||
async def cmd_calendar(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||
"""/calendar — today + tomorrow events."""
|
||||
await _fast_cmd(update, "calendar", list(context.args or []))
|
||||
|
||||
|
||||
async def cmd_calendarweek(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||
"""/calendarweek — this week's schedule."""
|
||||
await _fast_cmd(update, "calendar", ["week"])
|
||||
|
||||
|
||||
async def cmd_calendarbusy(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||
"""/calendarbusy — am I in a meeting?"""
|
||||
await _fast_cmd(update, "calendar", ["busy"])
|
||||
|
||||
|
||||
async def cmd_note(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||
"""/note <text>"""
|
||||
args = list(context.args or [])
|
||||
if not args:
|
||||
await update.message.reply_text("Usage: /note <text>")
|
||||
return
|
||||
await _fast_cmd(update, "note", args)
|
||||
|
||||
|
||||
async def cmd_jurnal(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||
"""/jurnal <text>"""
|
||||
args = list(context.args or [])
|
||||
if not args:
|
||||
await update.message.reply_text("Usage: /jurnal <text>")
|
||||
return
|
||||
await _fast_cmd(update, "jurnal", args)
|
||||
|
||||
|
||||
async def cmd_search(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||
"""/search <query>"""
|
||||
args = list(context.args or [])
|
||||
if not args:
|
||||
await update.message.reply_text("Usage: /search <query>")
|
||||
return
|
||||
await _fast_cmd(update, "search", args)
|
||||
|
||||
|
||||
async def cmd_kb(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||
"""/kb [category]"""
|
||||
await _fast_cmd(update, "kb", list(context.args or []))
|
||||
|
||||
|
||||
async def cmd_remind(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||
"""/remind <HH:MM> <text> or /remind <YYYY-MM-DD> <HH:MM> <text>"""
|
||||
args = list(context.args or [])
|
||||
if len(args) < 2:
|
||||
await update.message.reply_text("Usage: /remind <HH:MM> <text>")
|
||||
return
|
||||
await _fast_cmd(update, "remind", args)
|
||||
|
||||
|
||||
async def cmd_commit(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||
"""/commit [message]"""
|
||||
await _fast_cmd(update, "commit", list(context.args or []))
|
||||
|
||||
|
||||
async def cmd_push(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||
"""/push"""
|
||||
await _fast_cmd(update, "push", [])
|
||||
|
||||
|
||||
async def cmd_pull(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||
"""/pull"""
|
||||
await _fast_cmd(update, "pull", [])
|
||||
|
||||
|
||||
async def cmd_test(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||
"""/test [pattern]"""
|
||||
await _fast_cmd(update, "test", list(context.args or []))
|
||||
|
||||
|
||||
async def cmd_logs(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||
"""/logs [N]"""
|
||||
await _fast_cmd(update, "logs", list(context.args or []))
|
||||
|
||||
|
||||
async def cmd_doctor(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||
"""/doctor"""
|
||||
await _fast_cmd(update, "doctor", [])
|
||||
|
||||
|
||||
async def cmd_heartbeat_tg(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||
"""/heartbeat"""
|
||||
await _fast_cmd(update, "heartbeat", [])
|
||||
|
||||
|
||||
# --- Message handler ---
|
||||
|
||||
|
||||
@@ -371,6 +520,7 @@ def create_telegram_bot(config: Config, token: str) -> Application:
|
||||
|
||||
app = Application.builder().token(token).build()
|
||||
|
||||
# Core commands
|
||||
app.add_handler(CommandHandler("start", cmd_start))
|
||||
app.add_handler(CommandHandler("help", cmd_help))
|
||||
app.add_handler(CommandHandler("clear", cmd_clear))
|
||||
@@ -378,8 +528,59 @@ def create_telegram_bot(config: Config, token: str) -> Application:
|
||||
app.add_handler(CommandHandler("model", cmd_model))
|
||||
app.add_handler(CommandHandler("register", cmd_register))
|
||||
app.add_handler(CallbackQueryHandler(callback_model, pattern="^model:"))
|
||||
|
||||
# Fast commands
|
||||
app.add_handler(CommandHandler("email", cmd_email))
|
||||
app.add_handler(CommandHandler("emailsend", cmd_emailsend))
|
||||
app.add_handler(CommandHandler("emailsave", cmd_emailsave))
|
||||
app.add_handler(CommandHandler("calendar", cmd_calendar))
|
||||
app.add_handler(CommandHandler("calendarweek", cmd_calendarweek))
|
||||
app.add_handler(CommandHandler("calendarbusy", cmd_calendarbusy))
|
||||
app.add_handler(CommandHandler("note", cmd_note))
|
||||
app.add_handler(CommandHandler("jurnal", cmd_jurnal))
|
||||
app.add_handler(CommandHandler("search", cmd_search))
|
||||
app.add_handler(CommandHandler("kb", cmd_kb))
|
||||
app.add_handler(CommandHandler("remind", cmd_remind))
|
||||
app.add_handler(CommandHandler("commit", cmd_commit))
|
||||
app.add_handler(CommandHandler("push", cmd_push))
|
||||
app.add_handler(CommandHandler("pull", cmd_pull))
|
||||
app.add_handler(CommandHandler("test", cmd_test))
|
||||
app.add_handler(CommandHandler("logs", cmd_logs))
|
||||
app.add_handler(CommandHandler("doctor", cmd_doctor))
|
||||
app.add_handler(CommandHandler("heartbeat", cmd_heartbeat_tg))
|
||||
|
||||
# Text message handler (must be last)
|
||||
app.add_handler(
|
||||
MessageHandler(filters.TEXT & ~filters.COMMAND, handle_message)
|
||||
)
|
||||
|
||||
# Register bot menu commands on startup
|
||||
async def post_init(application: Application) -> None:
|
||||
await application.bot.set_my_commands([
|
||||
BotCommand("help", "List commands"),
|
||||
BotCommand("email", "Check unread emails"),
|
||||
BotCommand("emailsend", "Send an email"),
|
||||
BotCommand("emailsave", "Save emails to KB"),
|
||||
BotCommand("calendar", "Today + tomorrow events"),
|
||||
BotCommand("calendarweek", "Week schedule"),
|
||||
BotCommand("calendarbusy", "Am I busy?"),
|
||||
BotCommand("note", "Quick note"),
|
||||
BotCommand("jurnal", "Journal entry"),
|
||||
BotCommand("search", "Memory search"),
|
||||
BotCommand("kb", "KB notes"),
|
||||
BotCommand("remind", "Create reminder"),
|
||||
BotCommand("commit", "Git commit"),
|
||||
BotCommand("push", "Git push"),
|
||||
BotCommand("pull", "Git pull"),
|
||||
BotCommand("test", "Run tests"),
|
||||
BotCommand("clear", "Clear session"),
|
||||
BotCommand("status", "Session status"),
|
||||
BotCommand("model", "View/change model"),
|
||||
BotCommand("logs", "Show log lines"),
|
||||
BotCommand("doctor", "Diagnostics"),
|
||||
BotCommand("heartbeat", "Health checks"),
|
||||
])
|
||||
|
||||
app.post_init = post_init
|
||||
|
||||
return app
|
||||
|
||||
@@ -137,9 +137,16 @@ def _email_check() -> str:
|
||||
if count == 0:
|
||||
return "Inbox curat."
|
||||
emails = data.get("emails", [])
|
||||
subjects = [e.get("subject", "?") for e in emails[:5]]
|
||||
subject_list = ", ".join(subjects)
|
||||
return f"{count} necitite: {subject_list}"
|
||||
parts = [f"{count} necitite:\n"]
|
||||
for e in emails[:5]:
|
||||
sender = e.get("from", "?")
|
||||
subject = e.get("subject", "?")
|
||||
body = e.get("body_preview", "").strip()
|
||||
parts.append(f"**{subject}**\nDe la: {sender}")
|
||||
if body:
|
||||
parts.append(body[:500])
|
||||
parts.append("")
|
||||
return "\n".join(parts).strip()
|
||||
except json.JSONDecodeError:
|
||||
return proc.stdout.strip()[:300] if proc.stdout else "Email check: no output."
|
||||
except Exception as e:
|
||||
|
||||
Reference in New Issue
Block a user