#!/usr/bin/env python3 """odysseus-personal — shell wrapper for the personal-docs RAG index. `data/personal_docs/` holds local files indexed into RAG so chat can recall their content (`contacts.txt`, runbook fragments, etc.). This CLI lists indexed directories + manages the index. odysseus-personal list # files currently in the index odysseus-personal dirs # tracked directory roots odysseus-personal add-dir DIR odysseus-personal remove-dir DIR odysseus-personal reload # re-scan all dirs odysseus-personal exclude PATH # mark a file as excluded """ 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 src.personal_docs import PersonalDocsManager quiet_logs() except ModuleNotFoundError as e: sys.stderr.write(f"error: {e}\nhint: run from repo root with venv active.\n") sys.exit(2) _DATA_DIR = str(_REPO_ROOT / "data") _mgr: PersonalDocsManager | None = None def _manager() -> PersonalDocsManager: global _mgr if _mgr is None: _mgr = PersonalDocsManager(_DATA_DIR) return _mgr def _file_rows(files): return [f for f in files or [] if isinstance(f, dict)] def cmd_list(args): files = _file_rows(getattr(_manager(), "index", []) or []) out = [ {"name": f.get("name"), "size": f.get("size"), "path": f.get("path", "")} for f in files ] emit(out[: args.limit], args) def cmd_dirs(args): fn = getattr(_manager(), "get_indexed_directories", None) if fn is None: emit([], args) return emit(fn() or [], args) def cmd_add_dir(args): fn = getattr(_manager(), "add_directory", None) if fn is None: fail("PersonalDocsManager has no add_directory method on this version") fn(args.directory) emit({"ok": True, "directory": args.directory}, args) def cmd_remove_dir(args): fn = getattr(_manager(), "remove_directory", None) if fn is None: fail("PersonalDocsManager has no remove_directory method on this version") fn(args.directory) emit({"ok": True, "directory": args.directory}, args) def cmd_reload(args): _manager().refresh_index() emit({"ok": True, "count": len(_manager().index)}, args) def cmd_exclude(args): fn = getattr(_manager(), "exclude_file", None) if fn is None: fail("PersonalDocsManager has no exclude_file method on this version") fn(args.path) emit({"ok": True, "excluded": args.path}, args) def _build_parser(): common = argparse.ArgumentParser(add_help=False) common.add_argument("--pretty", action="store_true") p = argparse.ArgumentParser(prog="odysseus-personal", parents=[common]) sub = p.add_subparsers(dest="cmd", required=True) pl = sub.add_parser("list", parents=[common]) pl.add_argument("--limit", type=int, default=500) pl.set_defaults(func=cmd_list) pd = sub.add_parser("dirs", parents=[common]) pd.set_defaults(func=cmd_dirs) pa = sub.add_parser("add-dir", parents=[common]) pa.add_argument("directory") pa.set_defaults(func=cmd_add_dir) pr = sub.add_parser("remove-dir", parents=[common]) pr.add_argument("directory") pr.set_defaults(func=cmd_remove_dir) prl = sub.add_parser("reload", parents=[common]) prl.set_defaults(func=cmd_reload) pex = sub.add_parser("exclude", parents=[common]) pex.add_argument("path") pex.set_defaults(func=cmd_exclude) return p if __name__ == "__main__": sys.exit(run(_build_parser()))