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:
38
data/wisdom.json
Normal file
38
data/wisdom.json
Normal 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
237
tools/sysmon.py
Executable 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
160
tools/today.py
Executable 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
207
tools/wisdom.py
Executable 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
3
ws
@ -29,6 +29,9 @@ COMMANDS = {
|
|||||||
'habits': ('tools/habits.py', 'Habit tracker with streaks'),
|
'habits': ('tools/habits.py', 'Habit tracker with streaks'),
|
||||||
'track': ('tools/track.py', 'Time tracking'),
|
'track': ('tools/track.py', 'Time tracking'),
|
||||||
'cap': ('tools/capture.py', 'Quick thought capture'),
|
'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
|
# 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