#!/usr/bin/env python3 """Shadow Mode Runner — Silent observer for Family Logistics group. This script runs the shadow mode bot that: 1. Observes messages from the Family Logistics Telegram group 2. Runs tripwire detection on every message 3. Extracts entities using LLM when tripwire fires 4. Logs everything to shadow.db 5. NEVER SENDS MESSAGES (hard-disabled) Usage: cd /home/hoffmann_admin/.openclaw/workspace/services/icarus python run_shadow.py Environment: Requires shadow.env to be loaded (ENV=shadow) Safety: - SPEAK_ENABLED=False is hard-coded - Database is completely isolated (shadow.db) - No writes to staging or production - Bot is invisible to family members """ import os import sys import asyncio import logging from pathlib import Path # Load shadow.env BEFORE anything else reads env vars # This ensures TELEGRAM_BOT_TOKEN is set to the shadow bot token (8442671054) # and NOT inherited from the system env (8705120995 = Wadsworth) from dotenv import load_dotenv SCRIPT_DIR = Path(__file__).parent env_path = SCRIPT_DIR / "shadow.env" if env_path.exists(): load_dotenv(env_path, override=True) print(f"[run_shadow] Loaded env from {env_path}") else: print(f"[run_shadow] WARNING: {env_path} not found, using system env") # Add services directory to path for icarus package imports # The icarus package is at /home/hoffmann_admin/.openclaw/workspace/services/icarus # We add services/ so that 'import icarus.core...' works sys.path.insert(0, '/home/hoffmann_admin/.openclaw/workspace/services') # Set up logging before anything else logging.basicConfig( level=logging.INFO, format="%(asctime)s - [SHADOW] - %(levelname)s - %(message)s", ) # Verify shadow environment env = os.environ.get("ENV", "").lower() if env != "shadow": logging.error("=" * 60) logging.error("CRITICAL: ENV must be 'shadow' to run this script") logging.error("Current ENV=%r", env) logging.error("Run with: ENV=shadow python run_shadow.py") logging.error("=" * 60) sys.exit(1) # Verify we're not accidentally in wrong directory if "icarus" not in str(Path.cwd()): logging.error("CRITICAL: Must run from icarus directory") sys.exit(1) # Now import shadow modules from icarus.core.observer import ShadowDatabase, ShadowBot from icarus.core.observer.shadow_polling import shadow_polling_loop from icarus.core.config import shadow as shadow_config # Import family context import yaml def load_family_context(): """Load family context from YAML.""" config_path = Path(shadow_config.FAMILY_CONFIG_PATH) if not config_path.exists(): logging.error("Family config not found: %s", config_path) return {} try: with open(config_path) as f: return yaml.safe_load(f) except Exception as e: logging.error("Failed to load family context: %s", e) return {} async def main(): """Main shadow mode entry point.""" logging.info("=" * 60) logging.info("SHADOW MODE INITIALIZING") logging.info("=" * 60) # Safety checks assert not shadow_config.SPEAK_ENABLED, "CRITICAL: SPEAK_ENABLED is True" assert shadow_config.SHADOW_MODE, "CRITICAL: SHADOW_MODE is False" logging.info("Safety check passed:") logging.info(" SPEAK_ENABLED = %s", shadow_config.SPEAK_ENABLED) logging.info(" SHADOW_MODE = %s", shadow_config.SHADOW_MODE) logging.info(" DATA_DIR = %s", shadow_config.DATA_DIR) logging.info(" DB_PATH = %s", shadow_config.SHADOW_DB_PATH) # Initialize database logging.info("Initializing shadow database...") db = ShadowDatabase(shadow_config.SHADOW_DB_PATH) # Load family context logging.info("Loading family context...") family_context = load_family_context() if family_context: members = family_context.get("family", {}).get("members", []) logging.info(" Loaded %d family members", len(members)) for m in members: logging.info(" - %s (%s)", m.get("name"), m.get("role")) else: logging.warning(" No family context loaded!") # Initialize shadow bot logging.info("Initializing shadow bot...") verbose_dm = os.environ.get("VERBOSE_ADMIN_DM", "false").lower() == "true" bot = ShadowBot( db=db, bot_token=shadow_config.TELEGRAM_BOT_TOKEN, family_context=family_context, llm_url=shadow_config.LLM_URL, llm_model=shadow_config.LLM_MODEL, admin_chat_id=shadow_config.ADMIN_CHAT_ID, verbose_admin_dm=verbose_dm, ) logging.info("Shadow bot initialized") logging.info(" VERBOSE_ADMIN_DM = %s", verbose_dm) logging.info("Bot will observe messages from TELEGRAM_GROUP_ID=%s", shadow_config.TELEGRAM_GROUP_ID) # Safety confirmation logging.info("=" * 60) logging.info("SHADOW MODE ACTIVE") logging.info("The bot is now OBSERVING ONLY.") logging.info("It will NOT send any messages.") logging.info("It is INVISIBLE to family members.") logging.info("=" * 60) # Start polling try: await shadow_polling_loop(bot) except KeyboardInterrupt: logging.info("=" * 60) logging.info("SHADOW MODE SHUTTING DOWN") logging.info("=" * 60) except Exception as e: logging.exception("Shadow mode error: %s", e) raise if __name__ == "__main__": try: asyncio.run(main()) except Exception as e: logging.exception("Fatal error: %s", e) sys.exit(1)