Add goals, reading list, and people CRM - goals.py: Goal tracking with milestones and progress - reading.py: Reading list tracker (articles, books, papers) - people.py: Personal CRM for keeping notes on people - 21 tools total now
This commit is contained in:
42
data/goals.json
Normal file
42
data/goals.json
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
"goals": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"title": "Build out the sandbox into a daily driver",
|
||||||
|
"description": "",
|
||||||
|
"deadline": null,
|
||||||
|
"created": "2026-01-30T23:44:53.121855",
|
||||||
|
"status": "active",
|
||||||
|
"milestones": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"title": "Core tools built",
|
||||||
|
"done": true,
|
||||||
|
"created": "2026-01-30T23:44:53.147088",
|
||||||
|
"completed": "2026-01-30T23:44:53.248604"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"title": "Projects created",
|
||||||
|
"done": true,
|
||||||
|
"created": "2026-01-30T23:44:53.172153",
|
||||||
|
"completed": "2026-01-30T23:44:53.273691"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"title": "Git/Gitea integration",
|
||||||
|
"done": true,
|
||||||
|
"created": "2026-01-30T23:44:53.197877",
|
||||||
|
"completed": "2026-01-30T23:44:53.299278"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"title": "Documentation complete",
|
||||||
|
"done": false,
|
||||||
|
"created": "2026-01-30T23:44:53.223477"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"progress": 75
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
15
data/people.json
Normal file
15
data/people.json
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"d_j": {
|
||||||
|
"name": "D J",
|
||||||
|
"context": "My human, built this workspace together",
|
||||||
|
"added": "2026-01-30T23:45:35.014343",
|
||||||
|
"notes": [
|
||||||
|
{
|
||||||
|
"text": "Late night building session, very productive",
|
||||||
|
"date": "2026-01-30T23:46:25.644083"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": [],
|
||||||
|
"last_contact": "2026-01-30T23:46:25.644093"
|
||||||
|
}
|
||||||
|
}
|
||||||
22
data/reading.json
Normal file
22
data/reading.json
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"title": "The Art of Doing Science and Engineering",
|
||||||
|
"url": null,
|
||||||
|
"type": "book",
|
||||||
|
"tags": [],
|
||||||
|
"added": "2026-01-30T23:45:14.470706",
|
||||||
|
"status": "unread",
|
||||||
|
"notes": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"title": "Attention Is All You Need",
|
||||||
|
"url": null,
|
||||||
|
"type": "paper",
|
||||||
|
"tags": [],
|
||||||
|
"added": "2026-01-30T23:45:14.495258",
|
||||||
|
"status": "unread",
|
||||||
|
"notes": null
|
||||||
|
}
|
||||||
|
]
|
||||||
173
tools/goals.py
Executable file
173
tools/goals.py
Executable file
@ -0,0 +1,173 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
goals - Goal and project tracking with milestones
|
||||||
|
|
||||||
|
Track long-term goals and break them into actionable steps.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
WORKSPACE = Path("/home/wdjones/.openclaw/workspace")
|
||||||
|
GOALS_FILE = WORKSPACE / "data" / "goals.json"
|
||||||
|
|
||||||
|
def load_goals() -> dict:
|
||||||
|
"""Load goals data."""
|
||||||
|
GOALS_FILE.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
if GOALS_FILE.exists():
|
||||||
|
with open(GOALS_FILE) as f:
|
||||||
|
return json.load(f)
|
||||||
|
return {'goals': []}
|
||||||
|
|
||||||
|
def save_goals(data: dict):
|
||||||
|
"""Save goals data."""
|
||||||
|
with open(GOALS_FILE, 'w') as f:
|
||||||
|
json.dump(data, f, indent=2)
|
||||||
|
|
||||||
|
def add_goal(title: str, description: str = "", deadline: str = None):
|
||||||
|
"""Add a new goal."""
|
||||||
|
data = load_goals()
|
||||||
|
|
||||||
|
goal = {
|
||||||
|
'id': len(data['goals']) + 1,
|
||||||
|
'title': title,
|
||||||
|
'description': description,
|
||||||
|
'deadline': deadline,
|
||||||
|
'created': datetime.now().isoformat(),
|
||||||
|
'status': 'active',
|
||||||
|
'milestones': [],
|
||||||
|
'progress': 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
data['goals'].append(goal)
|
||||||
|
save_goals(data)
|
||||||
|
print(f"🎯 Goal #{goal['id']}: {title}")
|
||||||
|
|
||||||
|
def add_milestone(goal_id: int, title: str):
|
||||||
|
"""Add a milestone to a goal."""
|
||||||
|
data = load_goals()
|
||||||
|
|
||||||
|
for goal in data['goals']:
|
||||||
|
if goal['id'] == goal_id:
|
||||||
|
milestone = {
|
||||||
|
'id': len(goal['milestones']) + 1,
|
||||||
|
'title': title,
|
||||||
|
'done': False,
|
||||||
|
'created': datetime.now().isoformat(),
|
||||||
|
}
|
||||||
|
goal['milestones'].append(milestone)
|
||||||
|
save_goals(data)
|
||||||
|
print(f" ✓ Added milestone: {title}")
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"Goal not found: #{goal_id}")
|
||||||
|
|
||||||
|
def complete_milestone(goal_id: int, milestone_id: int):
|
||||||
|
"""Mark a milestone as complete."""
|
||||||
|
data = load_goals()
|
||||||
|
|
||||||
|
for goal in data['goals']:
|
||||||
|
if goal['id'] == goal_id:
|
||||||
|
for m in goal['milestones']:
|
||||||
|
if m['id'] == milestone_id:
|
||||||
|
m['done'] = True
|
||||||
|
m['completed'] = datetime.now().isoformat()
|
||||||
|
|
||||||
|
# Update progress
|
||||||
|
done = len([x for x in goal['milestones'] if x['done']])
|
||||||
|
total = len(goal['milestones'])
|
||||||
|
goal['progress'] = int(done / total * 100) if total > 0 else 0
|
||||||
|
|
||||||
|
save_goals(data)
|
||||||
|
print(f" ✓ Completed: {m['title']}")
|
||||||
|
print(f" Progress: {goal['progress']}%")
|
||||||
|
return
|
||||||
|
|
||||||
|
print("Milestone not found")
|
||||||
|
|
||||||
|
def show_goals(show_all: bool = False):
|
||||||
|
"""Display all goals."""
|
||||||
|
data = load_goals()
|
||||||
|
|
||||||
|
goals = data['goals']
|
||||||
|
if not show_all:
|
||||||
|
goals = [g for g in goals if g['status'] == 'active']
|
||||||
|
|
||||||
|
if not goals:
|
||||||
|
print("No active goals. Add one with: goals add <title>")
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"\n🎯 Goals ({len(goals)})")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
for goal in goals:
|
||||||
|
status = "✅" if goal['status'] == 'completed' else "🔵"
|
||||||
|
bar = "█" * (goal['progress'] // 10) + "░" * (10 - goal['progress'] // 10)
|
||||||
|
|
||||||
|
print(f"\n{status} #{goal['id']} {goal['title']}")
|
||||||
|
print(f" [{bar}] {goal['progress']}%")
|
||||||
|
|
||||||
|
if goal.get('deadline'):
|
||||||
|
print(f" 📅 Deadline: {goal['deadline']}")
|
||||||
|
|
||||||
|
if goal['milestones']:
|
||||||
|
for m in goal['milestones']:
|
||||||
|
check = "✓" if m['done'] else "○"
|
||||||
|
print(f" {check} {m['title']}")
|
||||||
|
|
||||||
|
print()
|
||||||
|
|
||||||
|
def complete_goal(goal_id: int):
|
||||||
|
"""Mark a goal as completed."""
|
||||||
|
data = load_goals()
|
||||||
|
|
||||||
|
for goal in data['goals']:
|
||||||
|
if goal['id'] == goal_id:
|
||||||
|
goal['status'] = 'completed'
|
||||||
|
goal['progress'] = 100
|
||||||
|
goal['completed'] = datetime.now().isoformat()
|
||||||
|
save_goals(data)
|
||||||
|
print(f"🎉 Goal completed: {goal['title']}")
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"Goal not found: #{goal_id}")
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
show_goals()
|
||||||
|
return
|
||||||
|
|
||||||
|
cmd = sys.argv[1]
|
||||||
|
|
||||||
|
if cmd == 'add' and len(sys.argv) > 2:
|
||||||
|
title = ' '.join(sys.argv[2:])
|
||||||
|
add_goal(title)
|
||||||
|
|
||||||
|
elif cmd == 'milestone' and len(sys.argv) > 3:
|
||||||
|
goal_id = int(sys.argv[2])
|
||||||
|
title = ' '.join(sys.argv[3:])
|
||||||
|
add_milestone(goal_id, title)
|
||||||
|
|
||||||
|
elif cmd == 'done' and len(sys.argv) > 3:
|
||||||
|
goal_id = int(sys.argv[2])
|
||||||
|
milestone_id = int(sys.argv[3])
|
||||||
|
complete_milestone(goal_id, milestone_id)
|
||||||
|
|
||||||
|
elif cmd == 'complete' and len(sys.argv) > 2:
|
||||||
|
complete_goal(int(sys.argv[2]))
|
||||||
|
|
||||||
|
elif cmd == 'list':
|
||||||
|
show_goals(show_all='--all' in sys.argv)
|
||||||
|
|
||||||
|
else:
|
||||||
|
print("Usage:")
|
||||||
|
print(" goals - Show active goals")
|
||||||
|
print(" goals add <title> - Add new goal")
|
||||||
|
print(" goals milestone <id> <text> - Add milestone")
|
||||||
|
print(" goals done <goal> <milestone> - Complete milestone")
|
||||||
|
print(" goals complete <id> - Complete goal")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
151
tools/people.py
Executable file
151
tools/people.py
Executable file
@ -0,0 +1,151 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
people - Personal CRM / people notes
|
||||||
|
|
||||||
|
Keep notes about people you know.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
WORKSPACE = Path("/home/wdjones/.openclaw/workspace")
|
||||||
|
PEOPLE_FILE = WORKSPACE / "data" / "people.json"
|
||||||
|
|
||||||
|
def load_people() -> dict:
|
||||||
|
"""Load people data."""
|
||||||
|
PEOPLE_FILE.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
if PEOPLE_FILE.exists():
|
||||||
|
with open(PEOPLE_FILE) as f:
|
||||||
|
return json.load(f)
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def save_people(data: dict):
|
||||||
|
"""Save people data."""
|
||||||
|
with open(PEOPLE_FILE, 'w') as f:
|
||||||
|
json.dump(data, f, indent=2)
|
||||||
|
|
||||||
|
def add_person(name: str, context: str = None):
|
||||||
|
"""Add a new person."""
|
||||||
|
people = load_people()
|
||||||
|
key = name.lower().replace(' ', '_')
|
||||||
|
|
||||||
|
if key in people:
|
||||||
|
print(f"Person already exists: {name}")
|
||||||
|
return
|
||||||
|
|
||||||
|
people[key] = {
|
||||||
|
'name': name,
|
||||||
|
'context': context,
|
||||||
|
'added': datetime.now().isoformat(),
|
||||||
|
'notes': [],
|
||||||
|
'tags': [],
|
||||||
|
'last_contact': None,
|
||||||
|
}
|
||||||
|
|
||||||
|
save_people(people)
|
||||||
|
print(f"👤 Added: {name}")
|
||||||
|
|
||||||
|
def add_note(name: str, note: str):
|
||||||
|
"""Add a note about someone."""
|
||||||
|
people = load_people()
|
||||||
|
search = name.lower().replace(' ', '')
|
||||||
|
|
||||||
|
# Find by partial match (normalize spaces)
|
||||||
|
matches = [k for k in people if search in k.replace('_', '') or search in people[k]['name'].lower().replace(' ', '')]
|
||||||
|
if not matches:
|
||||||
|
print(f"Person not found: {name}")
|
||||||
|
return
|
||||||
|
|
||||||
|
key = matches[0]
|
||||||
|
people[key]['notes'].append({
|
||||||
|
'text': note,
|
||||||
|
'date': datetime.now().isoformat(),
|
||||||
|
})
|
||||||
|
people[key]['last_contact'] = datetime.now().isoformat()
|
||||||
|
|
||||||
|
save_people(people)
|
||||||
|
print(f"📝 Note added for {people[key]['name']}")
|
||||||
|
|
||||||
|
def show_person(name: str):
|
||||||
|
"""Show info about a person."""
|
||||||
|
people = load_people()
|
||||||
|
search = name.lower().replace(' ', '')
|
||||||
|
|
||||||
|
# Find by partial match (normalize spaces)
|
||||||
|
matches = [k for k in people if search in k.replace('_', '') or search in people[k]['name'].lower().replace(' ', '')]
|
||||||
|
if not matches:
|
||||||
|
print(f"Person not found: {name}")
|
||||||
|
return
|
||||||
|
|
||||||
|
person = people[matches[0]]
|
||||||
|
|
||||||
|
print(f"\n👤 {person['name']}")
|
||||||
|
print("=" * 40)
|
||||||
|
|
||||||
|
if person.get('context'):
|
||||||
|
print(f"Context: {person['context']}")
|
||||||
|
|
||||||
|
if person.get('last_contact'):
|
||||||
|
last = person['last_contact'][:10]
|
||||||
|
print(f"Last contact: {last}")
|
||||||
|
|
||||||
|
if person['notes']:
|
||||||
|
print(f"\n📝 Notes ({len(person['notes'])})")
|
||||||
|
for note in person['notes'][-5:]:
|
||||||
|
date = note['date'][:10]
|
||||||
|
print(f" [{date}] {note['text'][:50]}")
|
||||||
|
|
||||||
|
print()
|
||||||
|
|
||||||
|
def list_people():
|
||||||
|
"""List all people."""
|
||||||
|
people = load_people()
|
||||||
|
|
||||||
|
if not people:
|
||||||
|
print("No people yet. Add with: people add <name>")
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"\n👥 People ({len(people)})")
|
||||||
|
print("=" * 40)
|
||||||
|
|
||||||
|
for key, person in sorted(people.items()):
|
||||||
|
notes = len(person['notes'])
|
||||||
|
context = person.get('context', '')[:20]
|
||||||
|
print(f" {person['name']:20} {notes} notes {context}")
|
||||||
|
|
||||||
|
print()
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
list_people()
|
||||||
|
return
|
||||||
|
|
||||||
|
cmd = sys.argv[1]
|
||||||
|
|
||||||
|
if cmd == 'add' and len(sys.argv) > 2:
|
||||||
|
name = sys.argv[2]
|
||||||
|
context = ' '.join(sys.argv[3:]) if len(sys.argv) > 3 else None
|
||||||
|
add_person(name, context)
|
||||||
|
|
||||||
|
elif cmd == 'note' and len(sys.argv) > 3:
|
||||||
|
name = sys.argv[2]
|
||||||
|
note = ' '.join(sys.argv[3:])
|
||||||
|
add_note(name, note)
|
||||||
|
|
||||||
|
elif cmd == 'show' and len(sys.argv) > 2:
|
||||||
|
show_person(sys.argv[2])
|
||||||
|
|
||||||
|
elif cmd == 'list':
|
||||||
|
list_people()
|
||||||
|
|
||||||
|
else:
|
||||||
|
print("Usage:")
|
||||||
|
print(" people - List all")
|
||||||
|
print(" people add <name> [context] - Add person")
|
||||||
|
print(" people note <name> <text> - Add note")
|
||||||
|
print(" people show <name> - Show person")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
163
tools/reading.py
Executable file
163
tools/reading.py
Executable file
@ -0,0 +1,163 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
reading - Reading list and article tracker
|
||||||
|
|
||||||
|
Track articles, books, and things to read.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
WORKSPACE = Path("/home/wdjones/.openclaw/workspace")
|
||||||
|
READING_FILE = WORKSPACE / "data" / "reading.json"
|
||||||
|
|
||||||
|
def load_list() -> list:
|
||||||
|
"""Load reading list."""
|
||||||
|
READING_FILE.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
if READING_FILE.exists():
|
||||||
|
with open(READING_FILE) as f:
|
||||||
|
return json.load(f)
|
||||||
|
return []
|
||||||
|
|
||||||
|
def save_list(items: list):
|
||||||
|
"""Save reading list."""
|
||||||
|
with open(READING_FILE, 'w') as f:
|
||||||
|
json.dump(items, f, indent=2)
|
||||||
|
|
||||||
|
def add(title: str, url: str = None, item_type: str = 'article', tags: list = None):
|
||||||
|
"""Add to reading list."""
|
||||||
|
items = load_list()
|
||||||
|
|
||||||
|
item = {
|
||||||
|
'id': len(items) + 1,
|
||||||
|
'title': title,
|
||||||
|
'url': url,
|
||||||
|
'type': item_type,
|
||||||
|
'tags': tags or [],
|
||||||
|
'added': datetime.now().isoformat(),
|
||||||
|
'status': 'unread',
|
||||||
|
'notes': None,
|
||||||
|
}
|
||||||
|
|
||||||
|
items.append(item)
|
||||||
|
save_list(items)
|
||||||
|
|
||||||
|
emoji = {'article': '📄', 'book': '📚', 'video': '🎬', 'paper': '📑'}.get(item_type, '📄')
|
||||||
|
print(f"{emoji} Added: {title}")
|
||||||
|
|
||||||
|
def show_list(status: str = None, limit: int = 20):
|
||||||
|
"""Show reading list."""
|
||||||
|
items = load_list()
|
||||||
|
|
||||||
|
if status:
|
||||||
|
items = [i for i in items if i['status'] == status]
|
||||||
|
|
||||||
|
if not items:
|
||||||
|
print("Reading list is empty")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Group by status
|
||||||
|
unread = [i for i in items if i['status'] == 'unread']
|
||||||
|
reading = [i for i in items if i['status'] == 'reading']
|
||||||
|
done = [i for i in items if i['status'] == 'done']
|
||||||
|
|
||||||
|
print(f"\n📚 Reading List")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
if reading:
|
||||||
|
print(f"\n📖 Currently Reading ({len(reading)})")
|
||||||
|
for item in reading:
|
||||||
|
print(f" #{item['id']} {item['title'][:40]}")
|
||||||
|
|
||||||
|
if unread:
|
||||||
|
print(f"\n📥 To Read ({len(unread)})")
|
||||||
|
for item in unread[:10]:
|
||||||
|
emoji = {'article': '📄', 'book': '📚', 'video': '🎬'}.get(item['type'], '📄')
|
||||||
|
print(f" #{item['id']} {emoji} {item['title'][:40]}")
|
||||||
|
if len(unread) > 10:
|
||||||
|
print(f" ... and {len(unread) - 10} more")
|
||||||
|
|
||||||
|
if done:
|
||||||
|
print(f"\n✅ Completed ({len(done)})")
|
||||||
|
for item in done[-5:]:
|
||||||
|
print(f" #{item['id']} {item['title'][:40]}")
|
||||||
|
|
||||||
|
print()
|
||||||
|
|
||||||
|
def start_reading(item_id: int):
|
||||||
|
"""Mark item as currently reading."""
|
||||||
|
items = load_list()
|
||||||
|
for item in items:
|
||||||
|
if item['id'] == item_id:
|
||||||
|
item['status'] = 'reading'
|
||||||
|
item['started'] = datetime.now().isoformat()
|
||||||
|
save_list(items)
|
||||||
|
print(f"📖 Started: {item['title']}")
|
||||||
|
return
|
||||||
|
print(f"Item not found: #{item_id}")
|
||||||
|
|
||||||
|
def finish_reading(item_id: int, notes: str = None):
|
||||||
|
"""Mark item as done."""
|
||||||
|
items = load_list()
|
||||||
|
for item in items:
|
||||||
|
if item['id'] == item_id:
|
||||||
|
item['status'] = 'done'
|
||||||
|
item['finished'] = datetime.now().isoformat()
|
||||||
|
if notes:
|
||||||
|
item['notes'] = notes
|
||||||
|
save_list(items)
|
||||||
|
print(f"✅ Finished: {item['title']}")
|
||||||
|
return
|
||||||
|
print(f"Item not found: #{item_id}")
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
show_list()
|
||||||
|
return
|
||||||
|
|
||||||
|
cmd = sys.argv[1]
|
||||||
|
|
||||||
|
if cmd == 'add' and len(sys.argv) > 2:
|
||||||
|
# Parse: add <title> [--url URL] [--type TYPE]
|
||||||
|
args = sys.argv[2:]
|
||||||
|
title_parts = []
|
||||||
|
url = None
|
||||||
|
item_type = 'article'
|
||||||
|
|
||||||
|
i = 0
|
||||||
|
while i < len(args):
|
||||||
|
if args[i] == '--url' and i + 1 < len(args):
|
||||||
|
url = args[i + 1]
|
||||||
|
i += 2
|
||||||
|
elif args[i] == '--type' and i + 1 < len(args):
|
||||||
|
item_type = args[i + 1]
|
||||||
|
i += 2
|
||||||
|
else:
|
||||||
|
title_parts.append(args[i])
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
add(' '.join(title_parts), url, item_type)
|
||||||
|
|
||||||
|
elif cmd == 'start' and len(sys.argv) > 2:
|
||||||
|
start_reading(int(sys.argv[2]))
|
||||||
|
|
||||||
|
elif cmd == 'done' and len(sys.argv) > 2:
|
||||||
|
item_id = int(sys.argv[2])
|
||||||
|
notes = ' '.join(sys.argv[3:]) if len(sys.argv) > 3 else None
|
||||||
|
finish_reading(item_id, notes)
|
||||||
|
|
||||||
|
elif cmd == 'list':
|
||||||
|
show_list()
|
||||||
|
|
||||||
|
else:
|
||||||
|
print("Usage:")
|
||||||
|
print(" reading - Show list")
|
||||||
|
print(" reading add <title> - Add article")
|
||||||
|
print(" reading add <title> --type book --url URL")
|
||||||
|
print(" reading start <id> - Start reading")
|
||||||
|
print(" reading done <id> [notes] - Finish reading")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
3
ws
3
ws
@ -34,6 +34,9 @@ COMMANDS = {
|
|||||||
'sys': ('tools/sysmon.py', 'System monitor'),
|
'sys': ('tools/sysmon.py', 'System monitor'),
|
||||||
'journal': ('tools/journal.py', 'Structured journaling'),
|
'journal': ('tools/journal.py', 'Structured journaling'),
|
||||||
'backup': ('tools/backup.py', 'Backup and export'),
|
'backup': ('tools/backup.py', 'Backup and export'),
|
||||||
|
'goals': ('tools/goals.py', 'Goal tracking'),
|
||||||
|
'reading': ('tools/reading.py', 'Reading list'),
|
||||||
|
'people': ('tools/people.py', 'Personal CRM'),
|
||||||
|
|
||||||
# Projects
|
# Projects
|
||||||
'news': ('projects/news-feed/main.py', 'RSS news reader'),
|
'news': ('projects/news-feed/main.py', 'RSS news reader'),
|
||||||
|
|||||||
Reference in New Issue
Block a user