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

14
data/decisions.json Normal file
View File

@ -0,0 +1,14 @@
[
{
"id": 1,
"title": "Build the sandbox as a daily driver",
"context": null,
"options_considered": [],
"reasoning": "Fresh environment, can customize fully, D J wants autonomous assistant",
"decided": "2026-01-30T23:53:41.544665",
"outcome": "Built 22 tools, 2 projects, full workspace in one night",
"outcome_date": "2026-01-30T23:53:41.595958",
"lessons": null,
"status": "good"
}
]

View File

@ -32,11 +32,12 @@
{
"id": 4,
"title": "Documentation complete",
"done": false,
"created": "2026-01-30T23:44:53.223477"
"done": true,
"created": "2026-01-30T23:44:53.223477",
"completed": "2026-01-30T23:47:41.400479"
}
],
"progress": 75
"progress": 100
}
]
}

View File

@ -18,7 +18,8 @@
},
"log": {
"2026-01-30": [
"journal"
"journal",
"reading"
]
}
}

32
data/ideas.json Normal file
View File

@ -0,0 +1,32 @@
[
{
"id": 1,
"title": "Voice memo transcription tool",
"description": null,
"tags": [],
"stage": "seed",
"notes": [],
"created": "2026-01-30T23:54:38.665336",
"updated": "2026-01-30T23:54:38.665342"
},
{
"id": 2,
"title": "Automatic workspace sync between machines",
"description": null,
"tags": [],
"stage": "seed",
"notes": [],
"created": "2026-01-30T23:54:38.691565",
"updated": "2026-01-30T23:54:38.691571"
},
{
"id": 3,
"title": "AI-powered daily briefing with news summarization",
"description": null,
"tags": [],
"stage": "seed",
"notes": [],
"created": "2026-01-30T23:54:38.718059",
"updated": "2026-01-30T23:54:38.718065"
}
]

35
data/metrics.json Normal file
View File

@ -0,0 +1,35 @@
{
"metrics": {
"tools_built": {
"name": "tools_built",
"unit": "",
"description": "Number of tools built",
"entries": [
{
"value": 22.0,
"timestamp": "2026-01-30T23:54:10.295568",
"note": "First day"
},
{
"value": 27.0,
"timestamp": "2026-01-30T23:55:32.463773",
"note": "After midnight push"
}
],
"created": "2026-01-30T23:54:10.266015"
},
"commits": {
"name": "commits",
"unit": "",
"description": "Git commits",
"entries": [
{
"value": 14.0,
"timestamp": "2026-01-30T23:54:10.354713",
"note": "First day"
}
],
"created": "2026-01-30T23:54:10.325021"
}
}
}

View File

@ -1,8 +1,8 @@
{
"current": {
"task": "More building",
"task": "Late night building - wine edition",
"project": "sandbox",
"start": "2026-01-30T23:27:15.604246"
"start": "2026-01-30T23:53:10.102195"
},
"entries": [
{
@ -11,6 +11,13 @@
"start": "2026-01-30T22:27:15.578348",
"end": "2026-01-30T23:27:15.578348",
"duration_min": 60
},
{
"task": "More building",
"project": "sandbox",
"start": "2026-01-30T23:27:15.604246",
"end": "2026-01-30T23:47:41.373585",
"duration_min": 20.4
}
]
}

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()

223
tools/ideas.py Executable file
View File

@ -0,0 +1,223 @@
#!/usr/bin/env python3
"""
ideas - Idea incubator
Capture, develop, and track ideas over time.
"""
import json
import sys
import random
from datetime import datetime
from pathlib import Path
WORKSPACE = Path("/home/wdjones/.openclaw/workspace")
IDEAS_FILE = WORKSPACE / "data" / "ideas.json"
STAGES = ['seed', 'growing', 'ready', 'planted', 'archived']
STAGE_EMOJI = {'seed': '🌱', 'growing': '🌿', 'ready': '🌻', 'planted': '🌳', 'archived': '📦'}
def load_ideas() -> list:
"""Load ideas."""
IDEAS_FILE.parent.mkdir(parents=True, exist_ok=True)
if IDEAS_FILE.exists():
with open(IDEAS_FILE) as f:
return json.load(f)
return []
def save_ideas(ideas: list):
"""Save ideas."""
with open(IDEAS_FILE, 'w') as f:
json.dump(ideas, f, indent=2)
def add_idea(title: str, description: str = None, tags: list = None):
"""Add a new idea."""
ideas = load_ideas()
idea = {
'id': len(ideas) + 1,
'title': title,
'description': description,
'tags': tags or [],
'stage': 'seed',
'notes': [],
'created': datetime.now().isoformat(),
'updated': datetime.now().isoformat(),
}
ideas.append(idea)
save_ideas(ideas)
print(f"🌱 Idea #{idea['id']}: {title}")
def add_note(idea_id: int, note: str):
"""Add a development note to an idea."""
ideas = load_ideas()
for idea in ideas:
if idea['id'] == idea_id:
idea['notes'].append({
'text': note,
'date': datetime.now().isoformat(),
})
idea['updated'] = datetime.now().isoformat()
save_ideas(ideas)
print(f"📝 Note added to idea #{idea_id}")
return
print(f"Idea not found: #{idea_id}")
def advance(idea_id: int):
"""Advance an idea to the next stage."""
ideas = load_ideas()
for idea in ideas:
if idea['id'] == idea_id:
current = idea['stage']
idx = STAGES.index(current)
if idx < len(STAGES) - 2: # Don't auto-advance to archived
idea['stage'] = STAGES[idx + 1]
idea['updated'] = datetime.now().isoformat()
save_ideas(ideas)
emoji = STAGE_EMOJI[idea['stage']]
print(f"{emoji} Idea #{idea_id} advanced to: {idea['stage']}")
else:
print("Idea already at final stage")
return
print(f"Idea not found: #{idea_id}")
def archive(idea_id: int):
"""Archive an idea."""
ideas = load_ideas()
for idea in ideas:
if idea['id'] == idea_id:
idea['stage'] = 'archived'
idea['updated'] = datetime.now().isoformat()
save_ideas(ideas)
print(f"📦 Idea #{idea_id} archived")
return
print(f"Idea not found: #{idea_id}")
def show_idea(idea_id: int):
"""Show idea details."""
ideas = load_ideas()
for idea in ideas:
if idea['id'] == idea_id:
emoji = STAGE_EMOJI[idea['stage']]
print(f"\n{emoji} Idea #{idea['id']}: {idea['title']}")
print("=" * 50)
print(f"Stage: {idea['stage']}")
print(f"Created: {idea['created'][:10]}")
if idea.get('description'):
print(f"\n{idea['description']}")
if idea.get('tags'):
print(f"\nTags: {' '.join('#' + t for t in idea['tags'])}")
if idea['notes']:
print(f"\n📝 Development Notes ({len(idea['notes'])})")
for note in idea['notes'][-5:]:
print(f" [{note['date'][:10]}] {note['text']}")
print()
return
print(f"Idea not found: #{idea_id}")
def list_ideas(stage: str = None):
"""List ideas."""
ideas = load_ideas()
if stage:
ideas = [i for i in ideas if i['stage'] == stage]
else:
ideas = [i for i in ideas if i['stage'] != 'archived']
if not ideas:
print("No ideas yet. Add with: ideas add <title>")
return
print(f"\n💡 Ideas ({len(ideas)})")
print("=" * 50)
for stage in STAGES[:-1]: # Exclude archived
stage_ideas = [i for i in ideas if i['stage'] == stage]
if stage_ideas:
emoji = STAGE_EMOJI[stage]
print(f"\n{emoji} {stage.title()} ({len(stage_ideas)})")
for idea in stage_ideas:
notes = len(idea['notes'])
print(f" #{idea['id']} {idea['title'][:35]} ({notes} notes)")
print()
def random_idea():
"""Get a random idea to work on."""
ideas = load_ideas()
active = [i for i in ideas if i['stage'] in ['seed', 'growing']]
if not active:
print("No active ideas")
return
idea = random.choice(active)
emoji = STAGE_EMOJI[idea['stage']]
print(f"\n🎲 Random idea to develop:\n")
print(f" {emoji} #{idea['id']} {idea['title']}")
if idea.get('description'):
print(f" {idea['description'][:60]}...")
print()
def main():
if len(sys.argv) < 2:
print("Usage:")
print(" ideas add <title> - Add new idea")
print(" ideas note <id> <text> - Add development note")
print(" ideas advance <id> - Move to next stage")
print(" ideas show <id> - Show idea details")
print(" ideas list [stage] - List ideas")
print(" ideas random - Random idea to work on")
print(" ideas archive <id> - Archive idea")
print("")
print(f"Stages: {''.join(STAGES[:-1])}")
list_ideas()
return
cmd = sys.argv[1]
if cmd == 'add' and len(sys.argv) > 2:
title = ' '.join(sys.argv[2:])
add_idea(title)
elif cmd == 'note' and len(sys.argv) > 3:
idea_id = int(sys.argv[2])
note = ' '.join(sys.argv[3:])
add_note(idea_id, note)
elif cmd == 'advance' and len(sys.argv) > 2:
advance(int(sys.argv[2]))
elif cmd == 'show' and len(sys.argv) > 2:
show_idea(int(sys.argv[2]))
elif cmd == 'list':
stage = sys.argv[2] if len(sys.argv) > 2 else None
list_ideas(stage)
elif cmd == 'random':
random_idea()
elif cmd == 'archive' and len(sys.argv) > 2:
archive(int(sys.argv[2]))
else:
print("Unknown command. Run 'ideas' for help.")
if __name__ == "__main__":
main()

208
tools/metrics.py Executable file
View File

@ -0,0 +1,208 @@
#!/usr/bin/env python3
"""
metrics - Track arbitrary metrics over time
Log numeric values and see trends.
"""
import json
import sys
from datetime import datetime, timedelta
from pathlib import Path
from statistics import mean, stdev
WORKSPACE = Path("/home/wdjones/.openclaw/workspace")
METRICS_FILE = WORKSPACE / "data" / "metrics.json"
def load_metrics() -> dict:
"""Load metrics data."""
METRICS_FILE.parent.mkdir(parents=True, exist_ok=True)
if METRICS_FILE.exists():
with open(METRICS_FILE) as f:
return json.load(f)
return {'metrics': {}}
def save_metrics(data: dict):
"""Save metrics data."""
with open(METRICS_FILE, 'w') as f:
json.dump(data, f, indent=2)
def define_metric(name: str, unit: str = '', description: str = ''):
"""Define a new metric to track."""
data = load_metrics()
key = name.lower().replace(' ', '_')
if key in data['metrics']:
print(f"Metric already exists: {name}")
return
data['metrics'][key] = {
'name': name,
'unit': unit,
'description': description,
'entries': [],
'created': datetime.now().isoformat(),
}
save_metrics(data)
print(f"📊 Created metric: {name}")
def log_value(name: str, value: float, note: str = None):
"""Log a value for a metric."""
data = load_metrics()
key = name.lower().replace(' ', '_')
# Find metric by partial match
matches = [k for k in data['metrics'] if key in k]
if not matches:
print(f"Metric not found: {name}")
print("Create with: metrics define <name>")
return
key = matches[0]
metric = data['metrics'][key]
entry = {
'value': value,
'timestamp': datetime.now().isoformat(),
'note': note,
}
metric['entries'].append(entry)
save_metrics(data)
unit = metric.get('unit', '')
print(f"📈 {metric['name']}: {value}{unit}")
def show_metric(name: str, days: int = 7):
"""Show metric details and trend."""
data = load_metrics()
key = name.lower().replace(' ', '_')
matches = [k for k in data['metrics'] if key in k]
if not matches:
print(f"Metric not found: {name}")
return
key = matches[0]
metric = data['metrics'][key]
entries = metric['entries']
print(f"\n📊 {metric['name']}")
if metric.get('description'):
print(f" {metric['description']}")
print("=" * 40)
if not entries:
print("No data yet")
return
# Filter to date range
cutoff = datetime.now() - timedelta(days=days)
recent = [e for e in entries if datetime.fromisoformat(e['timestamp']) > cutoff]
if not recent:
print(f"No data in last {days} days")
return
values = [e['value'] for e in recent]
unit = metric.get('unit', '')
# Stats
avg = mean(values)
latest = values[-1]
high = max(values)
low = min(values)
print(f"\n Last {days} days ({len(recent)} entries):")
print(f" Latest: {latest}{unit}")
print(f" Average: {avg:.1f}{unit}")
print(f" High: {high}{unit}")
print(f" Low: {low}{unit}")
# Trend
if len(values) >= 2:
first_half = mean(values[:len(values)//2])
second_half = mean(values[len(values)//2:])
if second_half > first_half * 1.05:
print(f" Trend: 📈 Up")
elif second_half < first_half * 0.95:
print(f" Trend: 📉 Down")
else:
print(f" Trend: ➡️ Stable")
# Recent entries
print(f"\n Recent:")
for e in recent[-5:]:
date = e['timestamp'][:10]
note = f" ({e['note']})" if e.get('note') else ""
print(f" {date}: {e['value']}{unit}{note}")
print()
def list_metrics():
"""List all metrics."""
data = load_metrics()
if not data['metrics']:
print("No metrics defined yet")
print("Create with: metrics define <name> [unit] [description]")
return
print(f"\n📊 Metrics ({len(data['metrics'])})")
print("=" * 40)
for key, metric in data['metrics'].items():
entries = len(metric['entries'])
unit = metric.get('unit', '')
if entries > 0:
latest = metric['entries'][-1]['value']
print(f" {metric['name']}: {latest}{unit} ({entries} entries)")
else:
print(f" {metric['name']}: (no data)")
print()
def main():
if len(sys.argv) < 2:
print("Usage:")
print(" metrics define <name> [unit] [description]")
print(" metrics log <name> <value> [note]")
print(" metrics show <name> [days]")
print(" metrics list")
print("")
print("Examples:")
print(" metrics define weight kg")
print(" metrics log weight 75.5")
print(" metrics show weight 30")
list_metrics()
return
cmd = sys.argv[1]
if cmd == 'define' and len(sys.argv) > 2:
name = sys.argv[2]
unit = sys.argv[3] if len(sys.argv) > 3 else ''
desc = ' '.join(sys.argv[4:]) if len(sys.argv) > 4 else ''
define_metric(name, unit, desc)
elif cmd == 'log' and len(sys.argv) > 3:
name = sys.argv[2]
value = float(sys.argv[3])
note = ' '.join(sys.argv[4:]) if len(sys.argv) > 4 else None
log_value(name, value, note)
elif cmd == 'show' and len(sys.argv) > 2:
name = sys.argv[2]
days = int(sys.argv[3]) if len(sys.argv) > 3 else 7
show_metric(name, days)
elif cmd == 'list':
list_metrics()
else:
print("Unknown command. Run 'metrics' for help.")
if __name__ == "__main__":
main()

126
tools/standup.py Executable file
View 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()

72
tools/weather.py Executable file
View File

@ -0,0 +1,72 @@
#!/usr/bin/env python3
"""
weather - Quick weather check
Get current weather and forecasts using wttr.in.
"""
import sys
import subprocess
from pathlib import Path
WORKSPACE = Path("/home/wdjones/.openclaw/workspace")
DEFAULT_LOCATION = "auto" # wttr.in auto-detects
def get_weather(location: str = None, detailed: bool = False):
"""Get weather for a location."""
loc = location or DEFAULT_LOCATION
loc = loc.replace(' ', '+')
if detailed:
# Full forecast
cmd = f'curl -s "wttr.in/{loc}?T"'
else:
# Compact format
cmd = f'curl -s "wttr.in/{loc}?format=%l:+%c+%t+%h+%w"'
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
output = result.stdout.strip()
if 'Unknown location' in output:
print(f"Location not found: {location}")
return
print(output)
def get_moon():
"""Get moon phase."""
cmd = 'curl -s "wttr.in/moon"'
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
print(result.stdout)
def main():
if len(sys.argv) < 2:
# Default: compact weather for auto location
get_weather()
return
cmd = sys.argv[1]
if cmd == 'moon':
get_moon()
elif cmd == 'full':
location = ' '.join(sys.argv[2:]) if len(sys.argv) > 2 else None
get_weather(location, detailed=True)
elif cmd in ['-h', '--help']:
print("Usage:")
print(" weather - Current weather (auto location)")
print(" weather <location> - Weather for location")
print(" weather full [loc] - Detailed forecast")
print(" weather moon - Moon phase")
print("")
print("Examples:")
print(" weather London")
print(" weather 'New York'")
print(" weather full Chicago")
else:
# Treat as location
location = ' '.join(sys.argv[1:])
get_weather(location)
if __name__ == "__main__":
main()

5
ws
View File

@ -38,6 +38,11 @@ COMMANDS = {
'reading': ('tools/reading.py', 'Reading list'),
'people': ('tools/people.py', 'Personal CRM'),
'ref': ('tools/ref.py', 'Quick reference snippets'),
'decide': ('tools/decide.py', 'Decision journal'),
'metrics': ('tools/metrics.py', 'Track metrics over time'),
'ideas': ('tools/ideas.py', 'Idea incubator'),
'weather': ('tools/weather.py', 'Weather check'),
'standup': ('tools/standup.py', 'Daily standup generator'),
# Projects
'news': ('projects/news-feed/main.py', 'RSS news reader'),