Spaces:
Runtime error
Runtime error
| #!/usr/bin/env python3 | |
| """odysseus-notes — shell wrapper around the notes feature. | |
| Reads the same SQLite the web UI uses. Output JSON; pipe-friendly. | |
| odysseus-notes list --label calendar | jq -r '.[] | .title' | |
| odysseus-notes show NOTE_ID | |
| odysseus-notes search "invoice" | |
| odysseus-notes create --title "Buy milk" --content "..." | |
| odysseus-notes delete NOTE_ID | |
| """ | |
| from __future__ import annotations | |
| import sys | |
| import os, sys | |
| sys.path.insert(0, os.path.join(os.path.dirname(__file__), "_lib")) | |
| from cli import quiet_logs, emit, fail, common_parser, run, REPO_ROOT as _REPO_ROOT | |
| quiet_logs() | |
| import argparse, json, logging, os, sys, uuid | |
| from datetime import datetime | |
| from pathlib import Path | |
| try: | |
| from core.database import SessionLocal, Note | |
| quiet_logs() | |
| except ModuleNotFoundError as e: | |
| sys.stderr.write(f"error: {e}\nhint: run from repo root with venv active.\n") | |
| sys.exit(2) | |
| def _load_items(raw) -> list: | |
| if not raw: | |
| return [] | |
| try: | |
| items = json.loads(raw) | |
| except (TypeError, json.JSONDecodeError): | |
| return [] | |
| return items if isinstance(items, list) else [] | |
| def _serialize(n: "Note") -> dict: | |
| return { | |
| "id": n.id, | |
| "title": n.title or "", | |
| "content": n.content or "", | |
| "items": _load_items(n.items), | |
| "note_type": n.note_type or "note", | |
| "color": n.color or "", | |
| "label": n.label or "", | |
| "pinned": bool(n.pinned), | |
| "archived": bool(n.archived), | |
| "due_date": n.due_date or "", | |
| "source": n.source or "user", | |
| "created_at": n.created_at.isoformat() if n.created_at else "", | |
| "updated_at": n.updated_at.isoformat() if n.updated_at else "", | |
| } | |
| def cmd_list(args): | |
| db = SessionLocal() | |
| try: | |
| q = db.query(Note) | |
| if not args.archived: | |
| q = q.filter(Note.archived == False) # noqa: E712 | |
| if args.label: | |
| q = q.filter(Note.label == args.label) | |
| if args.pinned: | |
| q = q.filter(Note.pinned == True) # noqa: E712 | |
| q = q.order_by(Note.pinned.desc(), Note.sort_order.asc(), Note.updated_at.desc()).limit(args.limit) | |
| emit([_serialize(n) for n in q.all()], args) | |
| finally: | |
| db.close() | |
| def cmd_show(args): | |
| db = SessionLocal() | |
| try: | |
| n = db.get(Note, args.id) | |
| if not n: | |
| fail(f"no note with id {args.id!r}") | |
| emit(_serialize(n), args) | |
| finally: | |
| db.close() | |
| def cmd_search(args): | |
| db = SessionLocal() | |
| try: | |
| like = f"%{args.query}%" | |
| rows = db.query(Note).filter( | |
| (Note.title.ilike(like)) | (Note.content.ilike(like)) | |
| ).order_by(Note.updated_at.desc()).limit(args.limit).all() | |
| emit([_serialize(n) for n in rows], args) | |
| finally: | |
| db.close() | |
| def cmd_create(args): | |
| db = SessionLocal() | |
| try: | |
| n = Note( | |
| id=str(uuid.uuid4()), | |
| title=args.title, | |
| content=args.content or "", | |
| note_type=args.type, | |
| color=args.color or None, | |
| label=args.label or None, | |
| pinned=bool(args.pin), | |
| source="user", | |
| ) | |
| db.add(n) | |
| db.commit() | |
| db.refresh(n) | |
| emit(_serialize(n), args) | |
| finally: | |
| db.close() | |
| def cmd_delete(args): | |
| db = SessionLocal() | |
| try: | |
| n = db.get(Note, args.id) | |
| if not n: | |
| fail(f"no note with id {args.id!r}") | |
| snap = _serialize(n) | |
| db.delete(n) | |
| db.commit() | |
| emit({"ok": True, "deleted": snap}, args) | |
| finally: | |
| db.close() | |
| def _build_parser(): | |
| common = argparse.ArgumentParser(add_help=False) | |
| common.add_argument("--pretty", action="store_true") | |
| p = argparse.ArgumentParser(prog="odysseus-notes", parents=[common]) | |
| sub = p.add_subparsers(dest="cmd", required=True) | |
| pl = sub.add_parser("list", parents=[common]) | |
| pl.add_argument("--label") | |
| pl.add_argument("--archived", action="store_true", help="include archived") | |
| pl.add_argument("--pinned", action="store_true", help="pinned only") | |
| pl.add_argument("--limit", type=int, default=50) | |
| pl.set_defaults(func=cmd_list) | |
| psh = sub.add_parser("show", parents=[common]) | |
| psh.add_argument("id") | |
| psh.set_defaults(func=cmd_show) | |
| ps = sub.add_parser("search", parents=[common]) | |
| ps.add_argument("query") | |
| ps.add_argument("--limit", type=int, default=50) | |
| ps.set_defaults(func=cmd_search) | |
| pc = sub.add_parser("create", parents=[common]) | |
| pc.add_argument("--title", required=True) | |
| pc.add_argument("--content", default="") | |
| pc.add_argument("--type", choices=["note", "checklist"], default="note") | |
| pc.add_argument("--color") | |
| pc.add_argument("--label") | |
| pc.add_argument("--pin", action="store_true") | |
| pc.set_defaults(func=cmd_create) | |
| pd = sub.add_parser("delete", parents=[common]) | |
| pd.add_argument("id") | |
| pd.set_defaults(func=cmd_delete) | |
| return p | |
| if __name__ == "__main__": | |
| sys.exit(run(_build_parser())) | |