Odysseus v1.0
This commit is contained in:
164
scripts/odysseus-notes
Executable file
164
scripts/odysseus-notes
Executable file
@@ -0,0 +1,164 @@
|
||||
#!/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 _serialize(n: "Note") -> dict:
|
||||
return {
|
||||
"id": n.id,
|
||||
"title": n.title or "",
|
||||
"content": n.content or "",
|
||||
"items": json.loads(n.items) if n.items else [],
|
||||
"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()))
|
||||
Reference in New Issue
Block a user