#!/usr/bin/env -S uv run --script # /// script # requires-python = ">=3.12" # dependencies = ["httpx>=0.27.0", "rich>=13.0.0"] # /// #MISE description="List Blumeops tasks from Todoist sorted by priority" """Fetch and display Blumeops tasks from Todoist, sorted by priority. This script is specific to Erich Blume's personal development workflow and is not intended for general use. It requires: - A 1Password CLI (`op`) configured with access to the author's vault - A Todoist account with a project named "Blumeops" The script fetches tasks and displays them sorted by a custom priority order: p1 (urgent), p2 (high), p4 (normal/default), p3 (backlog). The p3-last ordering reflects a deliberate choice to treat p3 as "backlog" rather than moderate priority. Usage: mise run blumeops-tasks """ import subprocess import sys import httpx from rich.console import Console from rich.text import Text TODOIST_API_BASE = "https://api.todoist.com/rest/v2" PROJECT_NAME = "Blumeops" # Priority mapping: Todoist API uses 1=normal(p4), 2=moderate(p3), 3=high(p2), 4=urgent(p1) # User wants order: p1, p2, p4, p3 (p3 is backlog, goes last) PRIORITY_LABELS = {4: "p1", 3: "p2", 1: "p4", 2: "p3"} PRIORITY_SORT_ORDER = {4: 1, 3: 2, 1: 3, 2: 4} # Lower = earlier def get_todoist_token() -> str: """Retrieve Todoist API token from 1Password.""" result = subprocess.run( [ "op", "--vault", "vg6xf6vvfmoh5hqjjhlhbeoaie", "item", "get", "c53h3xnmswhvexa5mntoyvhgpm", "--fields", "credential", "--reveal", ], capture_output=True, text=True, ) if result.returncode != 0: raise RuntimeError(f"Failed to get Todoist token from 1Password: {result.stderr}") return result.stdout.strip() def get_project_id(client: httpx.Client, project_name: str) -> str: """Find project ID by name.""" response = client.get(f"{TODOIST_API_BASE}/projects") response.raise_for_status() projects = response.json() for project in projects: if project["name"] == project_name: return project["id"] raise RuntimeError(f"Project '{project_name}' not found in Todoist") def get_tasks(client: httpx.Client, project_id: str) -> list[dict]: """Get all tasks for a project.""" response = client.get(f"{TODOIST_API_BASE}/tasks", params={"project_id": project_id}) response.raise_for_status() return response.json() def sort_tasks(tasks: list[dict]) -> list[dict]: """Sort tasks by custom priority order: p1, p2, p4, p3.""" return sorted(tasks, key=lambda t: PRIORITY_SORT_ORDER.get(t["priority"], 5)) def main() -> int: console = Console() # Get API token try: token = get_todoist_token() except RuntimeError as e: console.print(f"[red]Error:[/red] {e}") return 1 # Create HTTP client with auth header with httpx.Client(headers={"Authorization": f"Bearer {token}"}) as client: # Find project try: project_id = get_project_id(client, PROJECT_NAME) except RuntimeError as e: console.print(f"[red]Error:[/red] {e}") return 1 # Get and sort tasks tasks = get_tasks(client, project_id) sorted_tasks = sort_tasks(tasks) if not sorted_tasks: console.print("No tasks found in Blumeops project") return 0 # Display tasks console.print(f"[bold]Blumeops Tasks[/bold] ({len(sorted_tasks)} tasks)") console.print("=" * 40) console.print() for task in sorted_tasks: priority = task["priority"] label = PRIORITY_LABELS.get(priority, "p?") content = task["content"] description = task.get("description", "") # Header line with priority and content header = Text() header.append(f"[{label}]", style="bold") header.append(f" {content}") console.print(header) # Description indented if description: for line in description.split("\n"): console.print(f" {line}", style="dim") console.print() return 0 if __name__ == "__main__": sys.exit(main())