Add streak viewer - streak.py: Unified view of all streaks (habits, journal, notes, time) - 34 tools!
This commit is contained in:
@ -24,6 +24,11 @@
|
|||||||
"value": 33.0,
|
"value": 33.0,
|
||||||
"timestamp": "2026-01-30T23:58:26.472974",
|
"timestamp": "2026-01-30T23:58:26.472974",
|
||||||
"note": "Past midnight"
|
"note": "Past midnight"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": 34.0,
|
||||||
|
"timestamp": "2026-01-30T23:58:58.553542",
|
||||||
|
"note": "Last before midnight!"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"created": "2026-01-30T23:54:10.266015"
|
"created": "2026-01-30T23:54:10.266015"
|
||||||
|
|||||||
136
tools/streak.py
Executable file
136
tools/streak.py
Executable file
@ -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()
|
||||||
1
ws
1
ws
@ -49,6 +49,7 @@ COMMANDS = {
|
|||||||
'timer': ('tools/timer.py', 'Countdown and stopwatch'),
|
'timer': ('tools/timer.py', 'Countdown and stopwatch'),
|
||||||
'gen': ('tools/gen.py', 'Password, UUID, lorem, etc.'),
|
'gen': ('tools/gen.py', 'Password, UUID, lorem, etc.'),
|
||||||
'fortune': ('tools/fortune.py', 'Random wisdom'),
|
'fortune': ('tools/fortune.py', 'Random wisdom'),
|
||||||
|
'streak': ('tools/streak.py', 'View all streaks'),
|
||||||
|
|
||||||
# Projects
|
# Projects
|
||||||
'news': ('projects/news-feed/main.py', 'RSS news reader'),
|
'news': ('projects/news-feed/main.py', 'RSS news reader'),
|
||||||
|
|||||||
Reference in New Issue
Block a user