feat(01-03): add /archive command to compress and remove sessions

Archives session directory with tar+pigz to sessions_archive/,
terminates any running subprocess first, clears active session if needed.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Mikkel Georgsen 2026-02-04 18:01:14 +00:00
parent 3cc97adcd0
commit a27ac010ec
2 changed files with 79 additions and 0 deletions

View file

@ -140,6 +140,7 @@ async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
*Claude Sessions:*
/new <name> [persona] - Create new Claude session
/session <name> - Switch to a session
/archive <name> - 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 <name>")
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))

View file

@ -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.