diff --git a/telegram/bot.py b/telegram/bot.py index 9e51516..b6c66ab 100755 --- a/telegram/bot.py +++ b/telegram/bot.py @@ -140,6 +140,7 @@ async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE): *Claude Sessions:* /new [persona] - Create new Claude session /session - Switch to a session +/archive - Archive and remove a session *Status & Monitoring:* /status - Quick service overview @@ -407,6 +408,38 @@ async def switch_session_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE) logger.error(f"Error switching session: {e}") await update.message.reply_text(f"Error switching session: {e}") +async def archive_session_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE): + """Archive a Claude session (compress and remove).""" + if not is_authorized(update.effective_user.id): + return + + if not context.args: + await update.message.reply_text("Usage: /archive ") + return + + name = context.args[0] + + try: + # Terminate subprocess if running + if name in subprocesses: + if subprocesses[name].is_alive: + await subprocesses[name].terminate() + del subprocesses[name] + + # Archive the session + archive_path = session_manager.archive_session(name) + size_mb = archive_path.stat().st_size / (1024 * 1024) + await update.message.reply_text( + f"Session '{name}' archived ({size_mb:.1f} MB)\n{archive_path.name}" + ) + logger.info(f"Archived session '{name}' to {archive_path}") + + except ValueError as e: + await update.message.reply_text(str(e)) + except Exception as e: + logger.error(f"Error archiving session: {e}") + await update.message.reply_text(f"Error archiving session: {e}") + async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE): """Handle free text messages - route to active Claude session.""" if not is_authorized(update.effective_user.id): @@ -535,6 +568,7 @@ def main(): app.add_handler(CommandHandler("chatid", chatid)) app.add_handler(CommandHandler("new", new_session)) app.add_handler(CommandHandler("session", switch_session_cmd)) + app.add_handler(CommandHandler("archive", archive_session_cmd)) app.add_handler(CommandHandler("status", status)) app.add_handler(CommandHandler("pbs", pbs)) app.add_handler(CommandHandler("pbs_status", pbs_status)) diff --git a/telegram/session_manager.py b/telegram/session_manager.py index afb8f92..57457c0 100644 --- a/telegram/session_manager.py +++ b/telegram/session_manager.py @@ -21,6 +21,8 @@ Directory structure: import json import logging import re +import shutil +import subprocess from datetime import datetime, timezone from pathlib import Path from typing import Optional @@ -316,6 +318,49 @@ class SessionManager: """ return self.base_dir / name + def archive_session(self, name: str) -> Path: + """ + Archive a session by compressing it with tar+pigz and removing the original. + + Args: + name: Session name to archive + + Returns: + Path to the archive file + + Raises: + ValueError: If session does not exist + """ + if not self.session_exists(name): + raise ValueError(f"Session '{name}' does not exist") + + # Clear active session if archiving the active one + if self.active_session == name: + self.active_session = None + + # Create archive directory + archive_dir = self.base_dir.parent / "sessions_archive" + archive_dir.mkdir(parents=True, exist_ok=True) + + # Build archive filename with timestamp + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + archive_name = f"{name}_{timestamp}.tar.gz" + archive_path = archive_dir / archive_name + + # Compress with tar + pigz + session_dir = self.base_dir / name + subprocess.run( + ["tar", "--use-compress-program=pigz", "-cf", str(archive_path), + "-C", str(self.base_dir), name], + check=True, + ) + + # Remove original session directory + shutil.rmtree(session_dir) + + logger.info(f"Archived session '{name}' to {archive_path}") + return archive_path + def load_persona(self, name: str) -> dict: """ Load persona from library.