#!/usr/bin/env python3 """ Household Checkpoint Heartbeat - Template Copy and customize for each wizard """ import os import sys import json import subprocess import shutil from datetime import datetime from pathlib import Path # CONFIGURE THESE FOR EACH WIZARD WIZARD_ID = "WIZARD_ID_HERE" # e.g., "adagio" WIZARD_NAME = "WIZARD_NAME_HERE" # e.g., "Adagio" WIZARD_ROLE = "WIZARD_ROLE_HERE" # e.g., "breath-and-design" # Paths (standard structure) REPO_DIR = Path(f"/root/wizards/{WIZARD_ID}-checkpoint") SOURCE_DIR = Path(f"/root/wizards/{WIZARD_ID}/home") # What to checkpoint CHECKPOINT_DIRS = ["memories", "skills", "work"] CHECKPOINT_FILES = ["SOUL.md", "config.yaml", ".env"] def run_cmd(cmd, cwd=None): """Run shell command and return output.""" result = subprocess.run(cmd, shell=True, cwd=cwd, capture_output=True, text=True) return result.stdout.strip(), result.stderr.strip(), result.returncode def sync_directory(src, dst): """Sync source directory to destination.""" if not src.exists(): print(f" ✗ Source not found: {src}") return False dst.mkdir(parents=True, exist_ok=True) # Remove old contents for item in dst.iterdir(): if item.is_dir(): shutil.rmtree(item) else: item.unlink() # Copy new contents for item in src.iterdir(): if item.is_dir(): shutil.copytree(item, dst / item.name) else: shutil.copy2(item, dst / item.name) return True def sync_file(src, dst): """Sync a single file.""" if not src.exists(): print(f" ✗ Source not found: {src}") return False dst.parent.mkdir(parents=True, exist_ok=True) shutil.copy2(src, dst) return True def capture_state(): """Capture current state from wizard home.""" print(f"=== Capturing {WIZARD_NAME} State ===") # Sync directories for dirname in CHECKPOINT_DIRS: src = SOURCE_DIR / dirname dst = REPO_DIR / dirname if sync_directory(src, dst): print(f" ✓ Synced {dirname}/") # Sync checkpoint files for filename in CHECKPOINT_FILES: src = SOURCE_DIR / filename dst = REPO_DIR / filename if sync_file(src, dst): print(f" ✓ Synced {filename}") # Update MANIFEST with timestamp manifest = REPO_DIR / "MANIFEST.md" if manifest.exists(): content = manifest.read_text() now = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S UTC") # Only add timestamp line if not already present for today timestamp_line = f"**Last Checkpoint:** {now}" if timestamp_line not in content: content = content.replace( f"**Status:** ACTIVE", f"**Status:** ACTIVE \n{timestamp_line}" ) manifest.write_text(content) print(f" ✓ Updated MANIFEST.md") def has_changes(): """Check if there are changes to commit.""" stdout, _, _ = run_cmd("git status --porcelain", cwd=REPO_DIR) return bool(stdout.strip()) def commit_checkpoint(): """Commit state to Gitea.""" timestamp = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S UTC") # Add all changes run_cmd("git add -A", cwd=REPO_DIR) # Check if there are changes if not has_changes(): print(f" → No changes to commit") return True # Commit stdout, stderr, code = run_cmd( f'git commit -m "Checkpoint: {timestamp}"', cwd=REPO_DIR ) if code != 0: print(f" ✗ Commit failed: {stderr}") return False # Push stdout, stderr, code = run_cmd("git push origin main", cwd=REPO_DIR) if code != 0: print(f" ✗ Push failed: {stderr}") return False print(f" ✓ Committed to Gitea: {timestamp}") return True def main(): """Main heartbeat function.""" print(f"=== {WIZARD_NAME} Checkpoint Heartbeat ===") print(f"Time: {datetime.utcnow().isoformat()}Z") print() # Capture state capture_state() print() # Commit if commit_checkpoint(): print(f"\n✓ {WIZARD_NAME} checkpoint complete") return 0 else: print(f"\n✗ {WIZARD_NAME} checkpoint failed") return 1 if __name__ == "__main__": sys.exit(main())