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