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:
2026-01-30 23:55:37 -06:00
parent 9b7d7db55c
commit 3c9dc28852
12 changed files with 957 additions and 6 deletions

227
tools/decide.py Executable file
View File

@ -0,0 +1,227 @@
#!/usr/bin/env python3
"""
decide - Decision journal
Track decisions, their reasoning, and outcomes.
Learn from past choices.
"""
import json
import sys
from datetime import datetime
from pathlib import Path
WORKSPACE = Path("/home/wdjones/.openclaw/workspace")
DECISIONS_FILE = WORKSPACE / "data" / "decisions.json"
def load_decisions() -> list:
"""Load decisions."""
DECISIONS_FILE.parent.mkdir(parents=True, exist_ok=True)
if DECISIONS_FILE.exists():
with open(DECISIONS_FILE) as f:
return json.load(f)
return []
def save_decisions(data: list):
"""Save decisions."""
with open(DECISIONS_FILE, 'w') as f:
json.dump(data, f, indent=2)
def new_decision(title: str, context: str = None):
"""Record a new decision."""
decisions = load_decisions()
decision = {
'id': len(decisions) + 1,
'title': title,
'context': context,
'options_considered': [],
'reasoning': None,
'decided': datetime.now().isoformat(),
'outcome': None,
'outcome_date': None,
'lessons': None,
'status': 'pending', # pending, good, bad, mixed
}
decisions.append(decision)
save_decisions(decisions)
print(f"⚖️ Decision #{decision['id']}: {title}")
print(" Add reasoning with: decide reason <id> <text>")
def add_reasoning(decision_id: int, reasoning: str):
"""Add reasoning to a decision."""
decisions = load_decisions()
for d in decisions:
if d['id'] == decision_id:
d['reasoning'] = reasoning
save_decisions(decisions)
print(f"✓ Reasoning added to #{decision_id}")
return
print(f"Decision not found: #{decision_id}")
def record_outcome(decision_id: int, outcome: str, status: str = 'mixed'):
"""Record the outcome of a decision."""
decisions = load_decisions()
for d in decisions:
if d['id'] == decision_id:
d['outcome'] = outcome
d['outcome_date'] = datetime.now().isoformat()
d['status'] = status if status in ['good', 'bad', 'mixed'] else 'mixed'
save_decisions(decisions)
emoji = {'good': '', 'bad': '', 'mixed': '🔄'}[d['status']]
print(f"{emoji} Outcome recorded for #{decision_id}")
return
print(f"Decision not found: #{decision_id}")
def add_lesson(decision_id: int, lesson: str):
"""Add a lesson learned from a decision."""
decisions = load_decisions()
for d in decisions:
if d['id'] == decision_id:
d['lessons'] = lesson
save_decisions(decisions)
print(f"📚 Lesson added to #{decision_id}")
return
print(f"Decision not found: #{decision_id}")
def show_decision(decision_id: int):
"""Show details of a decision."""
decisions = load_decisions()
for d in decisions:
if d['id'] == decision_id:
status_emoji = {'pending': '', 'good': '', 'bad': '', 'mixed': '🔄'}
print(f"\n⚖️ Decision #{d['id']}: {d['title']}")
print("=" * 50)
print(f"Status: {status_emoji.get(d['status'], '')} {d['status']}")
print(f"Date: {d['decided'][:10]}")
if d.get('context'):
print(f"\n📋 Context:\n {d['context']}")
if d.get('reasoning'):
print(f"\n🤔 Reasoning:\n {d['reasoning']}")
if d.get('outcome'):
print(f"\n📊 Outcome:\n {d['outcome']}")
if d.get('lessons'):
print(f"\n📚 Lessons:\n {d['lessons']}")
print()
return
print(f"Decision not found: #{decision_id}")
def list_decisions(show_all: bool = False):
"""List all decisions."""
decisions = load_decisions()
if not show_all:
decisions = [d for d in decisions if d['status'] == 'pending']
if not decisions:
print("No decisions recorded" if show_all else "No pending decisions")
return
print(f"\n⚖️ Decisions ({len(decisions)})")
print("=" * 50)
status_emoji = {'pending': '', 'good': '', 'bad': '', 'mixed': '🔄'}
for d in decisions:
emoji = status_emoji.get(d['status'], '')
print(f" {emoji} #{d['id']} {d['title'][:40]}")
print(f" {d['decided'][:10]}")
print()
def review():
"""Review decisions that need outcomes."""
decisions = load_decisions()
pending = [d for d in decisions if d['status'] == 'pending']
if not pending:
print("No decisions pending review")
return
print(f"\n📋 Decisions Pending Review ({len(pending)})")
print("=" * 50)
for d in pending:
days = (datetime.now() - datetime.fromisoformat(d['decided'])).days
print(f" #{d['id']} {d['title'][:40]}")
print(f" {days} days ago — needs outcome")
print("\nRecord outcomes with: decide outcome <id> <text> --good/--bad/--mixed")
def main():
if len(sys.argv) < 2:
print("Usage:")
print(" decide new <title> - Record a decision")
print(" decide reason <id> <text> - Add reasoning")
print(" decide outcome <id> <text> [--good/--bad/--mixed]")
print(" decide lesson <id> <text> - Add lesson learned")
print(" decide show <id> - Show decision details")
print(" decide list [--all] - List decisions")
print(" decide review - Review pending decisions")
list_decisions()
return
cmd = sys.argv[1]
if cmd == 'new' and len(sys.argv) > 2:
title = ' '.join(sys.argv[2:])
new_decision(title)
elif cmd == 'reason' and len(sys.argv) > 3:
decision_id = int(sys.argv[2])
reasoning = ' '.join(sys.argv[3:])
add_reasoning(decision_id, reasoning)
elif cmd == 'outcome' and len(sys.argv) > 3:
decision_id = int(sys.argv[2])
status = 'mixed'
args = sys.argv[3:]
if '--good' in args:
status = 'good'
args.remove('--good')
elif '--bad' in args:
status = 'bad'
args.remove('--bad')
elif '--mixed' in args:
args.remove('--mixed')
outcome = ' '.join(args)
record_outcome(decision_id, outcome, status)
elif cmd == 'lesson' and len(sys.argv) > 3:
decision_id = int(sys.argv[2])
lesson = ' '.join(sys.argv[3:])
add_lesson(decision_id, lesson)
elif cmd == 'show' and len(sys.argv) > 2:
show_decision(int(sys.argv[2]))
elif cmd == 'list':
list_decisions(show_all='--all' in sys.argv)
elif cmd == 'review':
review()
else:
print("Unknown command. Run 'decide' for help.")
if __name__ == "__main__":
main()