Add daily dashboard, wisdom, and system monitor - today.py: Comprehensive daily overview combining all tools - wisdom.py: Quotes and wisdom collection with daily feature - sysmon.py: System resource monitor (CPU, memory, disk) - Updated ws CLI with new commands

This commit is contained in:
2026-01-30 23:39:27 -06:00
parent 6c4eb9284a
commit 6ee620e302
5 changed files with 645 additions and 0 deletions

38
data/wisdom.json Normal file
View File

@ -0,0 +1,38 @@
[
{
"id": 1,
"text": "The future is already here - it's just not evenly distributed.",
"category": "quote",
"source": "William Gibson",
"tags": [],
"added": "2026-01-30T23:38:38.352315",
"views": 0
},
{
"id": 2,
"text": "Simple is better than complex. Complex is better than complicated.",
"category": "principle",
"source": null,
"tags": [],
"added": "2026-01-30T23:38:38.378936",
"views": 0
},
{
"id": 3,
"text": "You can't optimize what you don't measure.",
"category": "lesson",
"source": null,
"tags": [],
"added": "2026-01-30T23:38:38.405313",
"views": 0
},
{
"id": 4,
"text": "The best time to plant a tree was 20 years ago. The second best time is now.",
"category": "reminder",
"source": null,
"tags": [],
"added": "2026-01-30T23:38:38.431626",
"views": 0
}
]

237
tools/sysmon.py Executable file
View File

@ -0,0 +1,237 @@
#!/usr/bin/env python3
"""
sysmon - System monitor
Quick system status and resource tracking.
"""
import os
import json
import subprocess
from datetime import datetime
from pathlib import Path
WORKSPACE = Path("/home/wdjones/.openclaw/workspace")
HISTORY_FILE = WORKSPACE / "data" / "sysmon_history.json"
def get_cpu_usage():
"""Get CPU usage percentage."""
try:
# Read /proc/stat for CPU usage
with open('/proc/stat') as f:
line = f.readline()
parts = line.split()
idle = int(parts[4])
total = sum(int(p) for p in parts[1:])
# Read again after a moment
import time
time.sleep(0.1)
with open('/proc/stat') as f:
line = f.readline()
parts = line.split()
idle2 = int(parts[4])
total2 = sum(int(p) for p in parts[1:])
idle_delta = idle2 - idle
total_delta = total2 - total
if total_delta == 0:
return 0
usage = 100 * (1 - idle_delta / total_delta)
return round(usage, 1)
except:
return None
def get_memory():
"""Get memory usage."""
try:
with open('/proc/meminfo') as f:
lines = f.readlines()
mem = {}
for line in lines:
parts = line.split()
key = parts[0].rstrip(':')
value = int(parts[1]) # in KB
mem[key] = value
total = mem.get('MemTotal', 0) / 1024 / 1024 # GB
available = mem.get('MemAvailable', 0) / 1024 / 1024 # GB
used = total - available
pct = (used / total * 100) if total > 0 else 0
return {
'total_gb': round(total, 1),
'used_gb': round(used, 1),
'available_gb': round(available, 1),
'percent': round(pct, 1),
}
except:
return None
def get_disk():
"""Get disk usage."""
try:
stat = os.statvfs('/')
total = stat.f_blocks * stat.f_frsize / 1024 / 1024 / 1024 # GB
free = stat.f_bavail * stat.f_frsize / 1024 / 1024 / 1024 # GB
used = total - free
pct = (used / total * 100) if total > 0 else 0
return {
'total_gb': round(total, 1),
'used_gb': round(used, 1),
'free_gb': round(free, 1),
'percent': round(pct, 1),
}
except:
return None
def get_load():
"""Get system load average."""
try:
with open('/proc/loadavg') as f:
parts = f.read().split()
return {
'load1': float(parts[0]),
'load5': float(parts[1]),
'load15': float(parts[2]),
}
except:
return None
def get_uptime():
"""Get system uptime."""
try:
with open('/proc/uptime') as f:
uptime_seconds = float(f.read().split()[0])
days = int(uptime_seconds // 86400)
hours = int((uptime_seconds % 86400) // 3600)
mins = int((uptime_seconds % 3600) // 60)
if days > 0:
return f"{days}d {hours}h {mins}m"
elif hours > 0:
return f"{hours}h {mins}m"
else:
return f"{mins}m"
except:
return None
def get_processes():
"""Get top processes by memory/CPU."""
try:
result = subprocess.run(
['ps', 'aux', '--sort=-rss'],
capture_output=True, text=True
)
lines = result.stdout.strip().split('\n')[1:6] # Skip header, top 5
processes = []
for line in lines:
parts = line.split(None, 10)
if len(parts) >= 11:
processes.append({
'user': parts[0],
'cpu': float(parts[2]),
'mem': float(parts[3]),
'cmd': parts[10][:30],
})
return processes
except:
return []
def bar(pct: float, width: int = 20) -> str:
"""Create a progress bar."""
filled = int(pct / 100 * width)
return "" * filled + "" * (width - filled)
def show_status():
"""Show current system status."""
print()
print("" + "" * 40 + "")
print(f"║ 🖥️ System Monitor {' ' * 20}")
print("" + "" * 40 + "")
# Uptime
uptime = get_uptime()
if uptime:
print(f"\n⏰ Uptime: {uptime}")
# CPU
cpu = get_cpu_usage()
if cpu is not None:
print(f"\n🔲 CPU: {bar(cpu)} {cpu}%")
# Memory
mem = get_memory()
if mem:
print(f"🔲 Memory: {bar(mem['percent'])} {mem['percent']}%")
print(f" {mem['used_gb']:.1f}GB / {mem['total_gb']:.1f}GB")
# Disk
disk = get_disk()
if disk:
print(f"🔲 Disk: {bar(disk['percent'])} {disk['percent']}%")
print(f" {disk['used_gb']:.0f}GB / {disk['total_gb']:.0f}GB")
# Load
load = get_load()
if load:
print(f"\n📊 Load: {load['load1']:.2f} / {load['load5']:.2f} / {load['load15']:.2f}")
# Top processes
procs = get_processes()
if procs:
print(f"\n🔝 Top Processes:")
for p in procs[:3]:
print(f" {p['mem']:.1f}% {p['cmd']}")
print()
def record():
"""Record current stats to history."""
HISTORY_FILE.parent.mkdir(parents=True, exist_ok=True)
if HISTORY_FILE.exists():
with open(HISTORY_FILE) as f:
history = json.load(f)
else:
history = []
entry = {
'timestamp': datetime.now().isoformat(),
'cpu': get_cpu_usage(),
'memory': get_memory(),
'disk': get_disk(),
'load': get_load(),
}
history.append(entry)
# Keep last 1000 entries
history = history[-1000:]
with open(HISTORY_FILE, 'w') as f:
json.dump(history, f)
print(f"✓ Recorded at {entry['timestamp'][:19]}")
def main():
import sys
if len(sys.argv) > 1:
cmd = sys.argv[1]
if cmd == 'record':
record()
else:
print("Usage: sysmon [record]")
else:
show_status()
if __name__ == "__main__":
main()

160
tools/today.py Executable file
View File

@ -0,0 +1,160 @@
#!/usr/bin/env python3
"""
today - Comprehensive daily overview
Combines tasks, habits, time tracking, notes, and captures into one view.
"""
import json
from datetime import datetime
from pathlib import Path
WORKSPACE = Path("/home/wdjones/.openclaw/workspace")
def load_json(path: Path) -> dict | list:
"""Load JSON file safely."""
if path.exists():
try:
with open(path) as f:
return json.load(f)
except:
pass
return {} if str(path).endswith('json') else []
def get_time_tracking():
"""Get today's time tracking."""
data = load_json(WORKSPACE / "data" / "timetrack.json")
today = datetime.now().strftime("%Y-%m-%d")
entries = [e for e in data.get('entries', []) if e['start'].startswith(today)]
current = data.get('current')
total_mins = sum(e['duration_min'] for e in entries)
return {
'current': current,
'entries': entries,
'total_mins': total_mins,
}
def get_habits():
"""Get today's habit status."""
data = load_json(WORKSPACE / "data" / "habits.json")
today = datetime.now().strftime("%Y-%m-%d")
habits = data.get('habits', {})
done = data.get('log', {}).get(today, [])
active = [(name, name in done) for name, info in habits.items()
if info.get('active', True)]
return {
'habits': active,
'done': len([h for h in active if h[1]]),
'total': len(active),
}
def get_captures():
"""Get unprocessed captures."""
data = load_json(WORKSPACE / "inbox" / "captures.json")
if isinstance(data, list):
return [c for c in data if not c.get('processed')]
return []
def get_tasks():
"""Get tasks from TASKS.md."""
tasks_file = WORKSPACE / "TASKS.md"
if not tasks_file.exists():
return {'in_progress': [], 'waiting': [], 'inbox': []}
result = {'in_progress': [], 'waiting': [], 'inbox': []}
section = None
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 = None
elif section and line.strip().startswith("- [ ]"):
task = line.strip()[6:].strip()
result[section].append(task)
return result
def get_notes():
"""Get today's notes."""
today = datetime.now().strftime("%Y-%m-%d")
notes_file = WORKSPACE / "memory" / f"{today}.md"
if not notes_file.exists():
return None
content = notes_file.read_text()
lines = [l for l in content.split('\n') if l.strip() and not l.startswith('#')]
return lines
def format_duration(mins: float) -> str:
"""Format minutes as human readable."""
if mins < 60:
return f"{mins:.0f}m"
hours = int(mins // 60)
m = int(mins % 60)
return f"{hours}h {m}m"
def main():
now = datetime.now()
print()
print("" + "" * 50 + "")
print(f"║ 🖤 Today: {now.strftime('%A, %B %d, %Y'):^38}")
print(f"{now.strftime('%H:%M'):^48}")
print("" + "" * 50 + "")
# Time tracking
time_data = get_time_tracking()
print(f"\n⏱️ Time Tracked: {format_duration(time_data['total_mins'])}")
if time_data['current']:
c = time_data['current']
print(f" └─ Currently: {c['task'][:35]}")
# Habits
habits = get_habits()
if habits['total'] > 0:
print(f"\n✅ Habits: {habits['done']}/{habits['total']}")
for name, done in habits['habits']:
status = "" if done else ""
print(f" {status} {name}")
# Tasks
tasks = get_tasks()
in_progress = len(tasks['in_progress'])
waiting = len(tasks['waiting'])
inbox = len(tasks['inbox'])
if in_progress + waiting + inbox > 0:
print(f"\n📋 Tasks: {in_progress} active, {waiting} waiting, {inbox} inbox")
for task in tasks['in_progress'][:3]:
print(f"{task[:40]}")
# Captures
captures = get_captures()
if captures:
print(f"\n📥 Inbox: {len(captures)} items")
for cap in captures[:3]:
emoji = {'idea': '💡', 'task': '', 'link': '🔗'}.get(cap.get('type'), '📝')
print(f" {emoji} {cap['content'][:35]}...")
# Notes
notes = get_notes()
if notes:
print(f"\n📝 Notes: {len(notes)} entries today")
# Quick actions
print("\n" + "" * 52)
print("Quick: ws habits check <name> | ws track start <task>")
print(" ws cap <thought> | ws note <text>")
print()
if __name__ == "__main__":
main()

207
tools/wisdom.py Executable file
View File

@ -0,0 +1,207 @@
#!/usr/bin/env python3
"""
wisdom - Collect and recall quotes, ideas, and wisdom
Store things worth remembering and get random inspiration.
"""
import json
import random
import sys
from datetime import datetime
from pathlib import Path
WORKSPACE = Path("/home/wdjones/.openclaw/workspace")
WISDOM_FILE = WORKSPACE / "data" / "wisdom.json"
CATEGORIES = {
'quote': '💬',
'idea': '💡',
'lesson': '📚',
'principle': '⚖️',
'reminder': '🔔',
'goal': '🎯',
}
def load_wisdom() -> list:
"""Load wisdom entries."""
WISDOM_FILE.parent.mkdir(parents=True, exist_ok=True)
if WISDOM_FILE.exists():
with open(WISDOM_FILE) as f:
return json.load(f)
return []
def save_wisdom(entries: list):
"""Save wisdom entries."""
with open(WISDOM_FILE, 'w') as f:
json.dump(entries, f, indent=2)
def add(text: str, category: str = 'quote', source: str = None, tags: list = None):
"""Add a new wisdom entry."""
entries = load_wisdom()
entry = {
'id': len(entries) + 1,
'text': text,
'category': category if category in CATEGORIES else 'quote',
'source': source,
'tags': tags or [],
'added': datetime.now().isoformat(),
'views': 0,
}
entries.append(entry)
save_wisdom(entries)
emoji = CATEGORIES.get(category, '💬')
print(f"{emoji} Added #{entry['id']}: {text[:50]}...")
def random_wisdom(category: str = None):
"""Get a random piece of wisdom."""
entries = load_wisdom()
if not entries:
print("No wisdom yet. Add some with: wisdom add <text>")
return
if category:
entries = [e for e in entries if e['category'] == category]
if not entries:
print(f"No entries in category: {category}")
return
entry = random.choice(entries)
entry['views'] += 1
save_wisdom(load_wisdom()) # Update view count
show_entry(entry)
def show_entry(entry: dict):
"""Display a wisdom entry nicely."""
emoji = CATEGORIES.get(entry['category'], '💬')
print()
print("" * 50)
print(f"{emoji} {entry['text']}")
if entry.get('source'):
print(f"{entry['source']}")
print("" * 50)
tags = ' '.join(f"#{t}" for t in entry.get('tags', []))
if tags:
print(f" {tags}")
print()
def list_wisdom(category: str = None, limit: int = 10):
"""List wisdom entries."""
entries = load_wisdom()
if category:
entries = [e for e in entries if e['category'] == category]
if not entries:
print("No wisdom found")
return
print(f"\n📚 Wisdom Collection ({len(entries)} entries)\n")
for entry in entries[-limit:]:
emoji = CATEGORIES.get(entry['category'], '💬')
text = entry['text'][:50]
if len(entry['text']) > 50:
text += "..."
print(f" #{entry['id']:3} {emoji} {text}")
print()
def search_wisdom(query: str):
"""Search wisdom entries."""
entries = load_wisdom()
query_lower = query.lower()
matches = []
for entry in entries:
searchable = f"{entry['text']} {entry.get('source', '')} {' '.join(entry.get('tags', []))}".lower()
if query_lower in searchable:
matches.append(entry)
if not matches:
print(f"No matches for: {query}")
return
print(f"\n🔍 Found {len(matches)} matches:\n")
for entry in matches:
emoji = CATEGORIES.get(entry['category'], '💬')
print(f" #{entry['id']} {emoji} {entry['text'][:50]}...")
print()
def daily():
"""Show the daily wisdom (same one each day, changes daily)."""
entries = load_wisdom()
if not entries:
print("No wisdom yet. Add some with: wisdom add <text>")
return
# Use the date as seed for consistent daily selection
day_seed = int(datetime.now().strftime("%Y%m%d"))
random.seed(day_seed)
entry = random.choice(entries)
random.seed() # Reset seed
print("\n🌅 Today's Wisdom:")
show_entry(entry)
def main():
if len(sys.argv) < 2:
print("Usage:")
print(" wisdom - Random wisdom")
print(" wisdom daily - Today's wisdom")
print(" wisdom add <text> - Add wisdom")
print(" wisdom add -c quote <text> - Add with category")
print(" wisdom add -s 'Author' <text> - Add with source")
print(" wisdom list [category] - List entries")
print(" wisdom search <query> - Search")
print("")
print(f"Categories: {', '.join(CATEGORIES.keys())}")
# Show random by default
random_wisdom()
return
cmd = sys.argv[1]
if cmd == 'daily':
daily()
elif cmd == 'add' and len(sys.argv) > 2:
# Parse arguments
args = sys.argv[2:]
category = 'quote'
source = None
# Check for flags
while args and args[0].startswith('-'):
if args[0] == '-c' and len(args) > 1:
category = args[1]
args = args[2:]
elif args[0] == '-s' and len(args) > 1:
source = args[1]
args = args[2:]
else:
args = args[1:]
text = ' '.join(args)
add(text, category, source)
elif cmd == 'list':
category = sys.argv[2] if len(sys.argv) > 2 else None
list_wisdom(category)
elif cmd == 'search' and len(sys.argv) > 2:
search_wisdom(' '.join(sys.argv[2:]))
else:
random_wisdom()
if __name__ == "__main__":
main()

3
ws
View File

@ -29,6 +29,9 @@ COMMANDS = {
'habits': ('tools/habits.py', 'Habit tracker with streaks'),
'track': ('tools/track.py', 'Time tracking'),
'cap': ('tools/capture.py', 'Quick thought capture'),
'today': ('tools/today.py', 'Daily overview dashboard'),
'wisdom': ('tools/wisdom.py', 'Quotes and wisdom collection'),
'sys': ('tools/sysmon.py', 'System monitor'),
# Projects
'news': ('projects/news-feed/main.py', 'RSS news reader'),