224 lines
6.5 KiB
Python
Executable File
224 lines
6.5 KiB
Python
Executable File
#!/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()
|