from __future__ import annotations import argparse import asyncio import json import sys from datetime import date from pathlib import Path from services.date_parser import parse_dates_file, parse_dates_text from services.fill_service import FillService def _print_json(data: object) -> None: print(json.dumps(data, ensure_ascii=False, indent=2)) def _resolve_dates(args: argparse.Namespace) -> list[date]: if args.file: return parse_dates_file(args.file) if args.dates: return parse_dates_text(args.dates) raise ValueError("Provide --file or --dates") async def cmd_status(_: argparse.Namespace) -> int: service = FillService() _print_json(await service.get_status()) return 0 async def cmd_teams_test(args: argparse.Namespace) -> int: """Standalone Teams calendar fetch test (no PTS / GitLab write).""" from services.teams_calendar_scraper import TeamsCalendarScraper start = date.fromisoformat(args.start) if args.start else date.today() end = date.fromisoformat(args.end) if args.end else start scraper = TeamsCalendarScraper() result = await scraper.list_meetings( start, end, skip_holidays=not args.include_holidays, debug=True, ) _print_json(result) if result.get("error"): return 1 if (result.get("total_count") or 0) == 0: print( "\nHINT: 0 meetings. Login Teams via Web UI Settings (MFA), then retry.", file=sys.stderr, ) return 2 return 0 async def cmd_preview(args: argparse.Namespace) -> int: service = FillService() start = date.fromisoformat(args.start) end = date.fromisoformat(args.end) _print_json(await service.preview_plan(start, end, use_llm=not args.no_llm)) return 0 async def cmd_preview_dates(args: argparse.Namespace) -> int: service = FillService() days = _resolve_dates(args) _print_json(await service.preview_plan_for_days(days, use_llm=not args.no_llm)) return 0 async def cmd_fill_today(args: argparse.Namespace) -> int: service = FillService() try: service.ensure_ready() except ValueError as exc: print(f"ERROR: {exc}", file=sys.stderr) return 1 result = await service.fill_today(dry_run=args.dry_run, use_llm=not args.no_llm) _print_json(result) return 0 if result.get("ok") else 1 async def cmd_fill_range(args: argparse.Namespace) -> int: service = FillService() try: service.ensure_ready() except ValueError as exc: print(f"ERROR: {exc}", file=sys.stderr) return 1 start = date.fromisoformat(args.start) end = date.fromisoformat(args.end) result = await service.fill_dates( start, end, dry_run=args.dry_run, skip_existing=not args.force, use_llm=not args.no_llm, ) _print_json(result) return 0 if result.get("ok") else 1 async def cmd_fill_dates(args: argparse.Namespace) -> int: service = FillService() try: service.ensure_ready() except ValueError as exc: print(f"ERROR: {exc}", file=sys.stderr) return 1 days = _resolve_dates(args) result = await service.fill_date_list( days, dry_run=args.dry_run, skip_existing=not args.force, use_llm=not args.no_llm, ) _print_json(result) return 0 if result.get("ok") else 1 def build_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser(description="PTS auto-fill CLI") sub = parser.add_subparsers(dest="command", required=True) sub.add_parser("status", help="Show system status") preview = sub.add_parser("preview", help="Preview fill plan for date range") preview.add_argument("--start", required=True, help="YYYY-MM-DD") preview.add_argument("--end", required=True, help="YYYY-MM-DD") preview.add_argument("--no-llm", action="store_true") preview_dates = sub.add_parser("preview-dates", help="Preview fill plan for date list") preview_dates.add_argument("--file", help="Path to dates.txt") preview_dates.add_argument("--dates", help="Comma/newline separated dates") preview_dates.add_argument("--no-llm", action="store_true") fill_today = sub.add_parser("fill-today", help="Fill today's PTS entries") fill_today.add_argument("--dry-run", action="store_true") fill_today.add_argument("--no-llm", action="store_true") fill_range = sub.add_parser("fill-range", help="Fill PTS entries for date range") fill_range.add_argument("--start", required=True, help="YYYY-MM-DD") fill_range.add_argument("--end", required=True, help="YYYY-MM-DD") fill_range.add_argument("--dry-run", action="store_true") fill_range.add_argument("--force", action="store_true") fill_range.add_argument("--no-llm", action="store_true") fill_dates = sub.add_parser("fill-dates", help="Fill PTS entries for explicit date list") fill_dates.add_argument("--file", help="Path to dates.txt") fill_dates.add_argument("--dates", help="Comma/newline separated dates") fill_dates.add_argument("--dry-run", action="store_true") fill_dates.add_argument("--force", action="store_true") fill_dates.add_argument("--no-llm", action="store_true") teams_test = sub.add_parser( "teams-test", help="Standalone test: fetch Teams/Outlook meetings only (no PTS write)", ) teams_test.add_argument("--start", help="YYYY-MM-DD (default: today)") teams_test.add_argument("--end", help="YYYY-MM-DD (default: start)") teams_test.add_argument( "--include-holidays", action="store_true", help="Do not skip weekends / TW holidays", ) return parser async def main() -> int: parser = build_parser() args = parser.parse_args() handlers = { "status": cmd_status, "preview": cmd_preview, "preview-dates": cmd_preview_dates, "fill-today": cmd_fill_today, "fill-range": cmd_fill_range, "fill-dates": cmd_fill_dates, "teams-test": cmd_teams_test, } try: return await handlers[args.command](args) except ValueError as exc: print(f"ERROR: {exc}", file=sys.stderr) return 1 if __name__ == "__main__": raise SystemExit(asyncio.run(main()))