Spaces:
Runtime error
Runtime error
| #!/usr/bin/env python3 | |
| """odysseus-docs — shell wrapper for the living-document store. | |
| Documents are the AI-editable text/markdown/code files the assistant | |
| creates in-place (think: a scratchpad it can revise). Each document | |
| has a content blob + version history. | |
| odysseus-docs list [--active] [--limit N] | |
| odysseus-docs show DOC_ID # current content + metadata | |
| odysseus-docs versions DOC_ID # version history (no content) | |
| odysseus-docs export DOC_ID --version N # full content of a specific version | |
| odysseus-docs search "invoice" # title + current_content match | |
| odysseus-docs delete DOC_ID # soft-delete (is_active=False) | |
| Reads `data/app.db` directly. | |
| """ | |
| 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 | |
| from pathlib import Path | |
| try: | |
| from core.database import SessionLocal, Document, DocumentVersion | |
| 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 _text_len(value) -> int: | |
| return len(value) if isinstance(value, str) else 0 | |
| def _serialize(d: "Document", include_content: bool = False) -> dict: | |
| out = { | |
| "id": d.id, | |
| "title": d.title, | |
| "language": d.language or "", | |
| "session_id": d.session_id or "", | |
| "version_count": d.version_count or 1, | |
| "is_active": bool(d.is_active), | |
| "tidy_verdict": d.tidy_verdict or "", | |
| "content_length": _text_len(d.current_content), | |
| "created_at": d.created_at.isoformat() if d.created_at else "", | |
| "updated_at": d.updated_at.isoformat() if d.updated_at else "", | |
| } | |
| if include_content: | |
| out["current_content"] = d.current_content or "" | |
| return out | |
| def cmd_list(args): | |
| db = SessionLocal() | |
| try: | |
| q = db.query(Document) | |
| if args.active: | |
| q = q.filter(Document.is_active == True) # noqa: E712 | |
| if args.session: | |
| q = q.filter(Document.session_id == args.session) | |
| q = q.order_by(Document.updated_at.desc()).limit(args.limit) | |
| emit([_serialize(d) for d in q.all()], args) | |
| finally: | |
| db.close() | |
| def cmd_show(args): | |
| db = SessionLocal() | |
| try: | |
| d = db.get(Document, args.id) | |
| if not d: | |
| fail(f"no document with id {args.id!r}") | |
| emit(_serialize(d, include_content=True), args) | |
| finally: | |
| db.close() | |
| def cmd_versions(args): | |
| db = SessionLocal() | |
| try: | |
| d = db.get(Document, args.id) | |
| if not d: | |
| fail(f"no document with id {args.id!r}") | |
| rows = db.query(DocumentVersion).filter( | |
| DocumentVersion.document_id == args.id | |
| ).order_by(DocumentVersion.version_number.desc()).all() | |
| emit([ | |
| { | |
| "version_number": v.version_number, | |
| "summary": v.summary or "", | |
| "source": v.source or "ai", | |
| "content_length": _text_len(v.content), | |
| } for v in rows | |
| ], args) | |
| finally: | |
| db.close() | |
| def cmd_export(args): | |
| """Print the full content of a specific version as the JSON | |
| payload's `content` field, or raw text via --raw.""" | |
| db = SessionLocal() | |
| try: | |
| d = db.get(Document, args.id) | |
| if not d: | |
| fail(f"no document with id {args.id!r}") | |
| if args.version is None: | |
| content = d.current_content or "" | |
| version = d.version_count or 1 | |
| else: | |
| v = db.query(DocumentVersion).filter( | |
| DocumentVersion.document_id == args.id, | |
| DocumentVersion.version_number == args.version, | |
| ).first() | |
| if not v: | |
| fail(f"no version {args.version} on doc {args.id!r}") | |
| content = v.content or "" | |
| version = v.version_number | |
| if args.raw: | |
| sys.stdout.write(content) | |
| if not content.endswith("\n"): | |
| sys.stdout.write("\n") | |
| return | |
| emit({ | |
| "id": d.id, | |
| "title": d.title, | |
| "version": version, | |
| "content": content, | |
| }, args) | |
| finally: | |
| db.close() | |
| def cmd_search(args): | |
| db = SessionLocal() | |
| try: | |
| like = f"%{args.query}%" | |
| rows = db.query(Document).filter( | |
| (Document.title.ilike(like)) | (Document.current_content.ilike(like)) | |
| ).order_by(Document.updated_at.desc()).limit(args.limit).all() | |
| emit([_serialize(d) for d in rows], args) | |
| finally: | |
| db.close() | |
| def cmd_delete(args): | |
| db = SessionLocal() | |
| try: | |
| d = db.get(Document, args.id) | |
| if not d: | |
| fail(f"no document with id {args.id!r}") | |
| d.is_active = False | |
| db.commit() | |
| emit({"ok": True, "id": d.id, "soft_deleted": True}, 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-docs", parents=[common]) | |
| sub = p.add_subparsers(dest="cmd", required=True) | |
| pl = sub.add_parser("list", parents=[common]) | |
| pl.add_argument("--active", action="store_true", help="only is_active=True") | |
| pl.add_argument("--session", help="filter by session_id") | |
| 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) | |
| pv = sub.add_parser("versions", parents=[common]) | |
| pv.add_argument("id") | |
| pv.set_defaults(func=cmd_versions) | |
| pe = sub.add_parser("export", parents=[common]) | |
| pe.add_argument("id") | |
| pe.add_argument("--version", type=int, help="specific version (default: current)") | |
| pe.add_argument("--raw", action="store_true", help="write raw content to stdout") | |
| pe.set_defaults(func=cmd_export) | |
| 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) | |
| 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())) | |