#!/usr/bin/env python3 """ track - Simple time tracking Log what you're working on and track time spent. """ import json import sys from datetime import datetime, timedelta from pathlib import Path WORKSPACE = Path("/home/wdjones/.openclaw/workspace") TRACK_FILE = WORKSPACE / "data" / "timetrack.json" def load_data() -> dict: """Load tracking data.""" TRACK_FILE.parent.mkdir(parents=True, exist_ok=True) if TRACK_FILE.exists(): with open(TRACK_FILE) as f: return json.load(f) return {'current': None, 'entries': []} def save_data(data: dict): """Save tracking data.""" with open(TRACK_FILE, 'w') as f: json.dump(data, f, indent=2) def start(task: str, project: str = None): """Start tracking a task.""" data = load_data() # Stop current task if running if data['current']: stop_current(data) data['current'] = { 'task': task, 'project': project, 'start': datetime.now().isoformat(), } save_data(data) print(f"⏱️ Started: {task}") if project: print(f" Project: {project}") def stop(): """Stop the current task.""" data = load_data() if not data['current']: print("Nothing being tracked") return stop_current(data) save_data(data) def stop_current(data: dict): """Stop and log current task.""" current = data['current'] start = datetime.fromisoformat(current['start']) end = datetime.now() duration = (end - start).total_seconds() / 60 # minutes entry = { 'task': current['task'], 'project': current.get('project'), 'start': current['start'], 'end': end.isoformat(), 'duration_min': round(duration, 1), } data['entries'].append(entry) data['current'] = None print(f"⏹️ Stopped: {current['task']}") print(f" Duration: {format_duration(duration)}") def format_duration(minutes: float) -> str: """Format minutes as human readable.""" if minutes < 60: return f"{minutes:.0f}m" hours = int(minutes // 60) mins = int(minutes % 60) return f"{hours}h {mins}m" def status(): """Show current tracking status.""" data = load_data() if data['current']: current = data['current'] start = datetime.fromisoformat(current['start']) elapsed = (datetime.now() - start).total_seconds() / 60 print(f"\n⏱️ Currently tracking:") print(f" Task: {current['task']}") if current.get('project'): print(f" Project: {current['project']}") print(f" Elapsed: {format_duration(elapsed)}") else: print("\n⏸️ Not tracking anything") # Today's summary today = datetime.now().strftime("%Y-%m-%d") today_entries = [e for e in data['entries'] if e['start'].startswith(today)] if today_entries: total = sum(e['duration_min'] for e in today_entries) print(f"\n📊 Today: {format_duration(total)} tracked") # Group by project by_project = {} for e in today_entries: proj = e.get('project') or 'No project' by_project[proj] = by_project.get(proj, 0) + e['duration_min'] for proj, mins in sorted(by_project.items(), key=lambda x: -x[1]): print(f" {proj}: {format_duration(mins)}") def report(days: int = 7): """Show time tracking report.""" data = load_data() cutoff = datetime.now() - timedelta(days=days) entries = [e for e in data['entries'] if datetime.fromisoformat(e['start']) > cutoff] if not entries: print(f"No entries in the last {days} days") return print(f"\n📊 Time Report (last {days} days)") print("=" * 40) total = sum(e['duration_min'] for e in entries) print(f"Total: {format_duration(total)}") # By project by_project = {} for e in entries: proj = e.get('project') or 'No project' by_project[proj] = by_project.get(proj, 0) + e['duration_min'] print(f"\nBy Project:") for proj, mins in sorted(by_project.items(), key=lambda x: -x[1]): pct = mins / total * 100 bar = "█" * int(pct / 5) print(f" {proj:20} {format_duration(mins):>8} {bar}") # By day by_day = {} for e in entries: day = e['start'][:10] by_day[day] = by_day.get(day, 0) + e['duration_min'] print(f"\nBy Day:") for day, mins in sorted(by_day.items()): bar = "█" * int(mins / 30) # 30 min per block print(f" {day} {format_duration(mins):>8} {bar}") def log_entry(task: str, minutes: int, project: str = None): """Log a past entry manually.""" data = load_data() end = datetime.now() start = end - timedelta(minutes=minutes) entry = { 'task': task, 'project': project, 'start': start.isoformat(), 'end': end.isoformat(), 'duration_min': minutes, } data['entries'].append(entry) save_data(data) print(f"✓ Logged: {task} ({format_duration(minutes)})") def main(): if len(sys.argv) < 2: print("Usage:") print(" track start [project] - Start tracking") print(" track stop - Stop current task") print(" track status - Show current status") print(" track report [days] - Show time report") print(" track log [proj] - Log past entry") print("") print("Examples:") print(" track start 'Writing docs' docs") print(" track stop") print(" track log 'Meeting' 30 work") status() return cmd = sys.argv[1] if cmd == 'start' and len(sys.argv) > 2: task = sys.argv[2] project = sys.argv[3] if len(sys.argv) > 3 else None start(task, project) elif cmd == 'stop': stop() elif cmd == 'status': status() elif cmd == 'report': days = int(sys.argv[2]) if len(sys.argv) > 2 else 7 report(days) elif cmd == 'log' and len(sys.argv) > 3: task = sys.argv[2] minutes = int(sys.argv[3]) project = sys.argv[4] if len(sys.argv) > 4 else None log_entry(task, minutes, project) else: print("Unknown command. Run 'track' for help.") if __name__ == "__main__": main()