157 lines
4.9 KiB
Python
157 lines
4.9 KiB
Python
#!/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())
|