import json from pathlib import Path import click from .models import Priority, Status from .store import Store @click.group() def main() -> None: pass @main.command() @click.option("--path", default=".agent_todos.db", help="SQLite DB path.") def init(path: str) -> None: Store(db_path=path) click.echo(f"Initialized TODO DB at {path}") @main.command() @click.argument("title") @click.option("-r", "--repo", default="", help="Repo slug, e.g. org/repo") @click.option("-p", "--priority", default="medium", type=click.Choice(["high", "medium", "low"])) @click.option("-a", "--assignee", default="") @click.option("-t", "--tags", multiple=True) @click.option("--path", default=".agent_todos.db") def create(title: str, repo: str, priority: str, assignee: str, tags: tuple[str, ...], path: str) -> None: todo = Store(db_path=path).create( _make_todo(title=title, repo=repo, priority=priority, assignee=assignee, tags=list(tags)) ) @main.command() @click.option("--repo") @click.option("--status", type=click.Choice(["open", "in_progress", "blocked", "resolved"])) @click.option("--json", "as_json", is_flag=True) @click.option("--path", default=".agent_todos.db") def list(repo, status, as_json, path): # type: ignore[no-untyped-def] s = Status(status) if status else None todos = Store(db_path=path).list(status=s, repo=repo) if as_json: click.echo(json.dumps([_serialize(t) for t in todos], indent=2)) else: for t in todos: click.echo(f"#{t.id} [{t.status.value}] {t.title} ({t.repo or 'no-repo'}) — p:{t.priority.value}") @main.command() @click.argument("todo_id", type=int) @click.option("-s", "--status", type=click.Choice(["open", "in_progress", "blocked", "resolved"])) @click.option("-t", "--title") @click.option("-p", "--priority", type=click.Choice(["high", "medium", "low"])) @click.option("-a", "--assignee") @click.option("--path", default=".agent_todos.db") def update(todo_id: int, status, title, priority, assignee, path: str) -> None: fields: dict = {} if status: fields["status"] = Status(status) if title: fields["title"] = title if priority: fields["priority"] = Priority(priority) if assignee is not None: fields["assignee"] = assignee todo = Store(db_path=path).update(todo_id, **fields) if todo: click.echo(f"Updated #{todo.id}: {todo.title} -> {todo.status.value}") else: raise click.ClickException("TODO not found") @main.command() @click.argument("todo_id", type=int) @click.option("--path", default=".agent_todos.db") def close(todo_id: int, path: str) -> None: todo = Store(db_path=path).update(todo_id, status=Status.resolved) if todo: click.echo(f"Resolved #{todo.id}: {todo.title}") else: raise click.ClickException("TODO not found") def _make_todo(*, title: str, repo: str, priority: str, assignee: str, tags: list[str]) -> Todo: return Todo(id=None, title=title, repo=repo, priority=Priority(priority), assignee=assignee, tags=tags) def _serialize(todo: Todo) -> dict: return { "id": todo.id, "title": todo.title, "repo": todo.repo, "status": todo.status.value, "priority": todo.priority.value, "assignee": todo.assignee, "tags": todo.tags, "created_at": todo.created_at, "updated_at": todo.updated_at, "resolved_at": todo.resolved_at, } if __name__ == "__main__": main()