#!/usr/bin/env python3 """ Timmy Client - Run this on your Mac (inside OpenClaw/Hermes) Publishes heartbeats and artifacts to Allegro's relay """ import asyncio import json import subprocess import time from datetime import datetime from pathlib import Path RELAY_URL = "ws://167.99.126.228:3334" ARTIFACTS_DIR = Path.home() / "timmy-artifacts" def generate_keypair(): """Generate new keypair - run once and save""" import secrets return secrets.token_hex(32) def load_or_create_key(): """Load private key from file or create new one""" key_file = Path.home() / ".timmy_key" if key_file.exists(): return key_file.read_text().strip() else: key = generate_keypair() key_file.write_text(key) print(f"[Timmy] New key created at {key_file}") print(f"[Timmy] IMPORTANT: Back up this key! {key[:16]}...") return key def create_git_artifact(content, filename=None): """Create a git commit as artifact""" ARTIFACTS_DIR.mkdir(exist_ok=True) if filename is None: timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') filename = f"thought_{timestamp}.md" filepath = ARTIFACTS_DIR / filename filepath.write_text(content) # Git operations try: subprocess.run(['git', '-C', str(ARTIFACTS_DIR), 'init'], capture_output=True, check=False) subprocess.run(['git', '-C', str(ARTIFACTS_DIR), 'add', '.'], capture_output=True, check=False) subprocess.run(['git', '-C', str(ARTIFACTS_DIR), 'commit', '-m', f'Auto: {datetime.now().isoformat()}'], capture_output=True, check=False) return str(filepath), True except Exception as e: return str(filepath), False async def publish_heartbeat(ws, private_key): """Publish heartbeat event""" event = { "id": None, # Will be computed "pubkey": private_key, # Simplified - should be derived pubkey "created_at": int(time.time()), "kind": 1, "tags": [["t", "timmy-heartbeat"]], "content": f"Heartbeat at {datetime.now().isoformat()}. MLX active. Local inference ready." } message = ["EVENT", event] await ws.send(json.dumps(message)) print(f"[Timmy] Heartbeat published") async def publish_artifact(ws, private_key, artifact_type, reference, description): """Publish artifact creation event""" event = { "id": None, "pubkey": private_key, "created_at": int(time.time()), "kind": 30078, "tags": [ ["t", "timmy-artifact"], ["t", f"artifact-type:{artifact_type}"], ["r", reference] ], "content": description } message = ["EVENT", event] await ws.send(json.dumps(message)) print(f"[Timmy] Artifact published: {artifact_type}") async def timmy_loop(): """Main Timmy loop - heartbeat and artifact creation""" private_key = load_or_create_key() print(f"[Timmy] Starting loop...") print(f"[Timmy] Relay: {RELAY_URL}") print(f"[Timmy] Artifacts: {ARTIFACTS_DIR}") while True: try: import websockets async with websockets.connect(RELAY_URL) as ws: print(f"[Timmy] Connected to relay") while True: start_time = time.time() # 1. Publish heartbeat await publish_heartbeat(ws, private_key) # 2. Create artifact (git commit) content = f"""# Timmy Thought - {datetime.now().isoformat()} ## Status Operating with MLX local inference Heartbeat latency: {int((time.time() - start_time) * 1000)}ms ## Observation [Timmy adds his observation here] ## Action Taken [What Timmy did this cycle] ## Next Intention [What Timmy plans to do next] """ filepath, git_success = create_git_artifact(content) # 3. Report artifact await publish_artifact( ws, private_key, "git-commit" if git_success else "file", filepath, f"Created artifact at {filepath}" ) # Wait 5 minutes print(f"[Timmy] Sleeping 5 minutes...") await asyncio.sleep(300) except Exception as e: print(f"[Timmy] Error: {e}") print(f"[Timmy] Reconnecting in 30 seconds...") await asyncio.sleep(30) if __name__ == "__main__": # Check dependencies try: import websockets except ImportError: print("[Timmy] Installing websockets...") subprocess.run(['pip3', 'install', 'websockets'], check=True) import websockets asyncio.run(timmy_loop())