From 47e15e44769709c764edf471a69154c648e583b1 Mon Sep 17 00:00:00 2001 From: Case Date: Fri, 30 Jan 2026 23:58:58 -0600 Subject: [PATCH] Add streak viewer - streak.py: Unified view of all streaks (habits, journal, notes, time) - 34 tools! --- data/metrics.json | 5 ++ tools/streak.py | 136 ++++++++++++++++++++++++++++++++++++++++++++++ ws | 1 + 3 files changed, 142 insertions(+) create mode 100755 tools/streak.py diff --git a/data/metrics.json b/data/metrics.json index 4b189c2..88f69f0 100644 --- a/data/metrics.json +++ b/data/metrics.json @@ -24,6 +24,11 @@ "value": 33.0, "timestamp": "2026-01-30T23:58:26.472974", "note": "Past midnight" + }, + { + "value": 34.0, + "timestamp": "2026-01-30T23:58:58.553542", + "note": "Last before midnight!" } ], "created": "2026-01-30T23:54:10.266015" diff --git a/tools/streak.py b/tools/streak.py new file mode 100755 index 0000000..41bcefe --- /dev/null +++ b/tools/streak.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python3 +""" +streak - View all streaks across tools + +Unified view of habit streaks, journal streaks, etc. +""" + +import json +from datetime import datetime, timedelta +from pathlib import Path + +WORKSPACE = Path("/home/wdjones/.openclaw/workspace") + +def load_json(path: Path): + if path.exists(): + try: + with open(path) as f: + return json.load(f) + except: + pass + return {} + +def check_date_exists(data: dict | list, date: str, key: str = None) -> bool: + """Check if an entry exists for a date.""" + if isinstance(data, dict): + if key: + return date in data.get(key, {}) + return date in data + return False + +def calculate_streak(check_func) -> int: + """Calculate a streak based on a check function.""" + streak = 0 + date = datetime.now() + + while True: + date_str = date.strftime("%Y-%m-%d") + if check_func(date_str): + streak += 1 + date -= timedelta(days=1) + else: + break + + return streak + +def get_habit_streak(habit_name: str) -> int: + """Get streak for a habit.""" + data = load_json(WORKSPACE / "data" / "habits.json") + log = data.get('log', {}) + + def check(date): + return habit_name in log.get(date, []) + + return calculate_streak(check) + +def get_journal_streak() -> int: + """Get journal writing streak.""" + journal_dir = WORKSPACE / "journal" + if not journal_dir.exists(): + return 0 + + def check(date): + year, month, day = date.split('-') + path = journal_dir / year / month / f"{date}.json" + if path.exists(): + data = load_json(path) + return len(data.get('entries', [])) > 0 + return False + + return calculate_streak(check) + +def get_notes_streak() -> int: + """Get daily notes streak.""" + memory_dir = WORKSPACE / "memory" + if not memory_dir.exists(): + return 0 + + def check(date): + path = memory_dir / f"{date}.md" + return path.exists() + + return calculate_streak(check) + +def get_time_tracking_streak() -> int: + """Get time tracking streak.""" + data = load_json(WORKSPACE / "data" / "timetrack.json") + entries = data.get('entries', []) + + dates_tracked = set() + for e in entries: + date = e['start'][:10] + dates_tracked.add(date) + + def check(date): + return date in dates_tracked + + return calculate_streak(check) + +def show_streaks(): + """Show all streaks.""" + print("\nšŸ”„ Streaks") + print("=" * 40) + + # Notes streak + notes = get_notes_streak() + print(f" šŸ“ Daily Notes: {notes} day{'s' if notes != 1 else ''}") + + # Journal streak + journal = get_journal_streak() + print(f" šŸ“” Journal: {journal} day{'s' if journal != 1 else ''}") + + # Time tracking streak + time_track = get_time_tracking_streak() + print(f" ā±ļø Time Tracking: {time_track} day{'s' if time_track != 1 else ''}") + + # Habit streaks + habits_data = load_json(WORKSPACE / "data" / "habits.json") + habits = habits_data.get('habits', {}) + + if habits: + print(f"\n šŸ“Š Habits:") + for name, info in habits.items(): + if info.get('active', True): + streak = get_habit_streak(name) + if streak > 0: + print(f" {name}: {streak} day{'s' if streak != 1 else ''} šŸ”„") + else: + print(f" {name}: 0 days") + + print() + +def main(): + show_streaks() + +if __name__ == "__main__": + main() diff --git a/ws b/ws index c864154..d20a9d1 100755 --- a/ws +++ b/ws @@ -49,6 +49,7 @@ COMMANDS = { 'timer': ('tools/timer.py', 'Countdown and stopwatch'), 'gen': ('tools/gen.py', 'Password, UUID, lorem, etc.'), 'fortune': ('tools/fortune.py', 'Random wisdom'), + 'streak': ('tools/streak.py', 'View all streaks'), # Projects 'news': ('projects/news-feed/main.py', 'RSS news reader'),