#!/usr/bin/env python3 """ Simple workspace dashboard - serves a status page on localhost. """ import http.server import socketserver import json import os from datetime import datetime, timedelta from pathlib import Path from urllib.parse import parse_qs, urlparse WORKSPACE = Path("/home/wdjones/.openclaw/workspace") PORT = 8080 def get_workspace_stats(): """Gather workspace statistics.""" stats = { 'generated': datetime.now().isoformat(), 'files': {'total': 0, 'by_type': {}}, 'recent': [], 'tasks': {'inbox': 0, 'in_progress': 0, 'waiting': 0, 'done_today': 0}, 'memory': {'today': None, 'days_logged': 0} } # Count files for root, dirs, files in os.walk(WORKSPACE): dirs[:] = [d for d in dirs if not d.startswith('.')] for fname in files: if fname.startswith('.'): continue stats['files']['total'] += 1 ext = Path(fname).suffix or 'no-ext' stats['files']['by_type'][ext] = stats['files']['by_type'].get(ext, 0) + 1 # Track recent files fpath = Path(root) / fname try: mtime = datetime.fromtimestamp(fpath.stat().st_mtime) if datetime.now() - mtime < timedelta(hours=24): rel = str(fpath.relative_to(WORKSPACE)) stats['recent'].append({'path': rel, 'modified': mtime.isoformat()}) except: pass stats['recent'] = sorted(stats['recent'], key=lambda x: x['modified'], reverse=True)[:10] # Parse tasks tasks_file = WORKSPACE / "TASKS.md" if tasks_file.exists(): section = None today = datetime.now().strftime("%Y-%m-%d") with open(tasks_file) as f: for line in f: if "## Inbox" in line: section = 'inbox' elif "## In Progress" in line: section = 'in_progress' elif "## Waiting" in line: section = 'waiting' elif "## Done" in line: section = 'done' elif line.strip().startswith("- ["): if section == 'done' and today in line: stats['tasks']['done_today'] += 1 elif section and section != 'done': stats['tasks'][section] += 1 # Memory stats memory_dir = WORKSPACE / "memory" if memory_dir.exists(): days = [f for f in memory_dir.iterdir() if f.suffix == '.md'] stats['memory']['days_logged'] = len(days) today_file = memory_dir / f"{datetime.now().strftime('%Y-%m-%d')}.md" if today_file.exists(): stats['memory']['today'] = len(today_file.read_text().split('\n')) return stats def generate_html(stats): """Generate dashboard HTML.""" return f""" Workspace Dashboard

🖤 Case Dashboard

Generated {stats['generated'][:19]}

📁 Files

{stats['files']['total']}
total files in workspace
{''.join(f'{ext}: {count}' for ext, count in sorted(stats['files']['by_type'].items(), key=lambda x: -x[1])[:5])}

✅ Tasks

{stats['tasks']['in_progress']}
in progress
{stats['tasks']['waiting']}
waiting
{stats['tasks']['inbox']}
inbox
{stats['tasks']['done_today']}
done today

📝 Memory

{stats['memory']['days_logged']}
days logged
Today: {stats['memory']['today'] or 0} lines

🕐 Recent Activity

""" class DashboardHandler(http.server.SimpleHTTPRequestHandler): def do_GET(self): if self.path == '/' or self.path == '/dashboard': stats = get_workspace_stats() html = generate_html(stats) self.send_response(200) self.send_header('Content-type', 'text/html') self.end_headers() self.wfile.write(html.encode()) elif self.path == '/api/stats': stats = get_workspace_stats() self.send_response(200) self.send_header('Content-type', 'application/json') self.end_headers() self.wfile.write(json.dumps(stats, indent=2).encode()) else: self.send_error(404) def log_message(self, format, *args): pass # Suppress logs def main(): with socketserver.TCPServer(("", PORT), DashboardHandler) as httpd: print(f"Dashboard running at http://localhost:{PORT}") print("Press Ctrl+C to stop") try: httpd.serve_forever() except KeyboardInterrupt: print("\nShutting down...") if __name__ == "__main__": main()