Add decision journal, metrics, ideas, weather, standup - decide.py: Decision journal with outcomes and lessons - metrics.py: Track arbitrary metrics over time - ideas.py: Idea incubator with stages - weather.py: Quick weather via wttr.in - standup.py: Daily standup generator - 27 tools total now
This commit is contained in:
126
tools/standup.py
Executable file
126
tools/standup.py
Executable file
@ -0,0 +1,126 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
standup - Generate daily standup notes
|
||||
|
||||
Pulls from yesterday's notes, today's tasks, and active work.
|
||||
"""
|
||||
|
||||
import json
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
|
||||
WORKSPACE = Path("/home/wdjones/.openclaw/workspace")
|
||||
|
||||
def load_json(path: Path):
|
||||
"""Load JSON file safely."""
|
||||
if path.exists():
|
||||
try:
|
||||
with open(path) as f:
|
||||
return json.load(f)
|
||||
except:
|
||||
pass
|
||||
return {} if 'json' in str(path) else []
|
||||
|
||||
def get_yesterday_notes():
|
||||
"""Get yesterday's activity from memory."""
|
||||
yesterday = (datetime.now() - timedelta(days=1)).strftime("%Y-%m-%d")
|
||||
notes_file = WORKSPACE / "memory" / f"{yesterday}.md"
|
||||
|
||||
if not notes_file.exists():
|
||||
return None
|
||||
|
||||
return notes_file.read_text()
|
||||
|
||||
def get_tasks():
|
||||
"""Get current tasks."""
|
||||
tasks_file = WORKSPACE / "TASKS.md"
|
||||
if not tasks_file.exists():
|
||||
return {'in_progress': [], 'waiting': []}
|
||||
|
||||
result = {'in_progress': [], 'waiting': []}
|
||||
section = None
|
||||
|
||||
with open(tasks_file) as f:
|
||||
for line in f:
|
||||
if "## In Progress" in line: section = 'in_progress'
|
||||
elif "## Waiting" in line: section = 'waiting'
|
||||
elif "## Done" in line or "## Inbox" in line: section = None
|
||||
elif section and line.strip().startswith("- [ ]"):
|
||||
task = line.strip()[6:].strip()
|
||||
result[section].append(task)
|
||||
|
||||
return result
|
||||
|
||||
def get_time_tracked():
|
||||
"""Get yesterday's time tracking."""
|
||||
data = load_json(WORKSPACE / "data" / "timetrack.json")
|
||||
yesterday = (datetime.now() - timedelta(days=1)).strftime("%Y-%m-%d")
|
||||
|
||||
entries = [e for e in data.get('entries', []) if e['start'].startswith(yesterday)]
|
||||
|
||||
if not entries:
|
||||
return None
|
||||
|
||||
total_mins = sum(e['duration_min'] for e in entries)
|
||||
by_project = {}
|
||||
for e in entries:
|
||||
proj = e.get('project') or 'general'
|
||||
by_project[proj] = by_project.get(proj, 0) + e['duration_min']
|
||||
|
||||
return {'total_mins': total_mins, 'by_project': by_project}
|
||||
|
||||
def get_blockers():
|
||||
"""Get waiting/blocked items as potential blockers."""
|
||||
tasks = get_tasks()
|
||||
return tasks.get('waiting', [])
|
||||
|
||||
def format_duration(mins: float) -> str:
|
||||
if mins < 60:
|
||||
return f"{mins:.0f}m"
|
||||
hours = int(mins // 60)
|
||||
m = int(mins % 60)
|
||||
return f"{hours}h {m}m"
|
||||
|
||||
def generate_standup():
|
||||
"""Generate standup notes."""
|
||||
now = datetime.now()
|
||||
|
||||
print(f"\n📋 Daily Standup - {now.strftime('%A, %B %d, %Y')}")
|
||||
print("=" * 50)
|
||||
|
||||
# Yesterday
|
||||
print("\n**Yesterday:**")
|
||||
time_data = get_time_tracked()
|
||||
if time_data:
|
||||
print(f" • Tracked {format_duration(time_data['total_mins'])} of work")
|
||||
for proj, mins in time_data['by_project'].items():
|
||||
print(f" - {proj}: {format_duration(mins)}")
|
||||
else:
|
||||
print(" • (no time tracked)")
|
||||
|
||||
# Today
|
||||
print("\n**Today:**")
|
||||
tasks = get_tasks()
|
||||
if tasks['in_progress']:
|
||||
for task in tasks['in_progress'][:5]:
|
||||
print(f" • {task[:50]}")
|
||||
else:
|
||||
print(" • (no tasks in progress)")
|
||||
|
||||
# Blockers
|
||||
print("\n**Blockers:**")
|
||||
blockers = get_blockers()
|
||||
if blockers:
|
||||
for blocker in blockers[:3]:
|
||||
print(f" ⚠️ {blocker[:50]}")
|
||||
else:
|
||||
print(" • None")
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
print()
|
||||
|
||||
def main():
|
||||
generate_standup()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user