Add capture tool - Quick thought/task/idea/link capture - Goes to inbox for later processing - Multiple types with emoji indicators - Export to markdown

This commit is contained in:
2026-01-30 23:28:29 -06:00
parent 71504e3265
commit 6c4eb9284a
3 changed files with 204 additions and 0 deletions

26
inbox/captures.json Normal file
View File

@ -0,0 +1,26 @@
[
{
"id": 1,
"content": "Build a voice memo transcription tool",
"type": "idea",
"tags": [],
"captured": "2026-01-30T23:28:05.330819",
"processed": false
},
{
"id": 2,
"content": "Set up automatic backups",
"type": "task",
"tags": [],
"captured": "2026-01-30T23:28:05.356440",
"processed": false
},
{
"id": 3,
"content": "This is a random thought",
"type": "note",
"tags": [],
"captured": "2026-01-30T23:28:05.381974",
"processed": false
}
]

177
tools/capture.py Executable file
View File

@ -0,0 +1,177 @@
#!/usr/bin/env python3
"""
capture - Quick thought capture
Quickly capture thoughts, tasks, ideas, and links.
They go to inbox for later processing.
"""
import json
import sys
from datetime import datetime
from pathlib import Path
WORKSPACE = Path("/home/wdjones/.openclaw/workspace")
CAPTURE_FILE = WORKSPACE / "inbox" / "captures.json"
TYPES = {
'thought': '💭',
'task': '',
'idea': '💡',
'link': '🔗',
'note': '📝',
'quote': '💬',
'question': '',
}
def load_captures() -> list:
"""Load captured items."""
CAPTURE_FILE.parent.mkdir(parents=True, exist_ok=True)
if CAPTURE_FILE.exists():
with open(CAPTURE_FILE) as f:
return json.load(f)
return []
def save_captures(captures: list):
"""Save captures."""
with open(CAPTURE_FILE, 'w') as f:
json.dump(captures, f, indent=2)
def capture(content: str, capture_type: str = 'note', tags: list = None):
"""Capture something quickly."""
captures = load_captures()
item = {
'id': len(captures) + 1,
'content': content,
'type': capture_type if capture_type in TYPES else 'note',
'tags': tags or [],
'captured': datetime.now().isoformat(),
'processed': False,
}
captures.append(item)
save_captures(captures)
emoji = TYPES.get(capture_type, '📝')
print(f"{emoji} Captured #{item['id']}: {content[:50]}...")
def list_captures(show_all: bool = False, type_filter: str = None):
"""List captured items."""
captures = load_captures()
if not show_all:
captures = [c for c in captures if not c.get('processed')]
if type_filter:
captures = [c for c in captures if c['type'] == type_filter]
if not captures:
print("📭 Inbox empty")
return
print(f"\n📥 Inbox ({len(captures)} items)")
print("=" * 50)
for item in captures:
emoji = TYPES.get(item['type'], '📝')
status = "" if item.get('processed') else " "
content = item['content'][:60]
if len(item['content']) > 60:
content += "..."
print(f"[{status}] #{item['id']:3} {emoji} {content}")
if item.get('tags'):
tags = ' '.join(f"#{t}" for t in item['tags'])
print(f" {tags}")
print()
def process_item(item_id: int):
"""Mark an item as processed."""
captures = load_captures()
for item in captures:
if item['id'] == item_id:
item['processed'] = True
save_captures(captures)
print(f"✓ Processed #{item_id}")
return
print(f"Item not found: #{item_id}")
def clear_processed():
"""Remove processed items."""
captures = load_captures()
unprocessed = [c for c in captures if not c.get('processed')]
removed = len(captures) - len(unprocessed)
save_captures(unprocessed)
print(f"✓ Removed {removed} processed items")
def export_markdown():
"""Export unprocessed items as markdown."""
captures = load_captures()
unprocessed = [c for c in captures if not c.get('processed')]
if not unprocessed:
print("Nothing to export")
return
# Group by type
by_type = {}
for item in unprocessed:
t = item['type']
if t not in by_type:
by_type[t] = []
by_type[t].append(item)
print("# Inbox Export")
print(f"*Exported: {datetime.now().strftime('%Y-%m-%d %H:%M')}*\n")
for type_name, items in sorted(by_type.items()):
emoji = TYPES.get(type_name, '📝')
print(f"## {emoji} {type_name.title()}s\n")
for item in items:
tags = ' '.join(f"`#{t}`" for t in item.get('tags', []))
print(f"- {item['content']} {tags}")
print()
def main():
if len(sys.argv) < 2:
print("Usage:")
print(" capture <text> - Quick capture")
print(" capture -t task <text> - Capture as task")
print(" capture -t idea <text> - Capture as idea")
print(" capture -t link <url> - Capture a link")
print(" capture list - Show inbox")
print(" capture list --all - Show all (incl. processed)")
print(" capture done <id> - Mark as processed")
print(" capture clear - Clear processed items")
print(" capture export - Export as markdown")
print("")
print(f"Types: {', '.join(TYPES.keys())}")
list_captures()
return
# Parse args
if sys.argv[1] == 'list':
show_all = '--all' in sys.argv
list_captures(show_all=show_all)
elif sys.argv[1] == 'done' and len(sys.argv) > 2:
process_item(int(sys.argv[2]))
elif sys.argv[1] == 'clear':
clear_processed()
elif sys.argv[1] == 'export':
export_markdown()
elif sys.argv[1] == '-t' and len(sys.argv) > 3:
capture_type = sys.argv[2]
content = ' '.join(sys.argv[3:])
capture(content, capture_type)
else:
content = ' '.join(sys.argv[1:])
capture(content)
if __name__ == "__main__":
main()

1
ws
View File

@ -28,6 +28,7 @@ COMMANDS = {
'focus': ('tools/focus.py', 'Pomodoro focus timer'),
'habits': ('tools/habits.py', 'Habit tracker with streaks'),
'track': ('tools/track.py', 'Time tracking'),
'cap': ('tools/capture.py', 'Quick thought capture'),
# Projects
'news': ('projects/news-feed/main.py', 'RSS news reader'),