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:
227
tools/decide.py
Executable file
227
tools/decide.py
Executable 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()
|
||||
Reference in New Issue
Block a user