#!/usr/bin/env python3 """Tiny CLI todo tracker. Data lives in ~/.todo.json.""" import argparse import json import os from datetime import datetime, timezone DEFAULT_PATH = os.path.expanduser("~/.todo.json") def load(path=DEFAULT_PATH): if not os.path.exists(path): return [] with open(path, "r", encoding="utf-8") as f: return json.load(f) def save(items, path=DEFAULT_PATH): with open(path, "w", encoding="utf-8") as f: json.dump(items, f, indent=2) def cmd_add(args): items = load() entry = { "id": max((i.get("id", 0) for i in items), default=0) + 1, "task": args.task, "done": False, "created": datetime.now(timezone.utc).isoformat(), } items.append(entry) save(items) print(f"added {entry['id']}: {args.task}") def cmd_list(args): items = load() if args.done_only: items = [i for i in items if i.get("done")] elif not args.all: items = [i for i in items if not i.get("done")] for i in items: status = "x" if i.get("done") else " " print(f"[{status}] {i['id']}: {i['task']}") if not items: print("(none)") def cmd_done(args): items = load() for i in items: if i.get("id") == args.id: i["done"] = True i["finished"] = datetime.now(timezone.utc).isoformat() save(items) print(f"done {i['id']}") return print(f"id {args.id} not found", flush=True) raise SystemExit(2) def main(): p = argparse.ArgumentParser(description="Tiny todo CLI") sub = p.add_subparsers(dest="cmd") a = sub.add_parser("add") a.add_argument("task", help="task text") sub.add_parser("list", help="show todos") sub.add_parser("all", help="show all including done") d = sub.add_parser("done", help="show done only or mark todo done by id") d.add_argument("id", type=int, nargs="?", help="todo id to mark done") args = p.parse_args() if args.cmd == "add": cmd_add(args) elif args.cmd == "list": cmd_list(args) elif args.cmd == "all": cmd_list(args) elif args.cmd == "done": if args.id is not None: cmd_done(args) else: args.done_only = True cmd_list(args) else: p.print_help() raise SystemExit(1) if __name__ == "__main__": main()