#!/usr/bin/env python3 """odysseus-tasks — shell wrapper for the scheduled-tasks feature. odysseus-tasks list [--status active|paused|completed] [--limit N] odysseus-tasks show TASK_ID odysseus-tasks pause TASK_ID odysseus-tasks resume TASK_ID odysseus-tasks runs [--task TASK_ID] [--limit N] """ 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, ScheduledTask, TaskRun 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 _preview_text(value, limit: int = 200) -> str: text = value if isinstance(value, str) else "" return text[:limit] + ("…" if len(text) > limit else "") def _serialize_task(t: "ScheduledTask") -> dict: return { "id": t.id, "name": t.name, "task_type": t.task_type, "action": t.action, "prompt": _preview_text(t.prompt), "schedule": t.schedule, "scheduled_time": t.scheduled_time, "next_run": t.next_run.isoformat() if t.next_run else "", "last_run": t.last_run.isoformat() if t.last_run else "", "status": t.status, "model": t.model, "run_count": t.run_count or 0, "cron_expression": t.cron_expression or "", } def _serialize_run(r: "TaskRun") -> dict: return { "id": r.id, "task_id": r.task_id, "started_at": r.started_at.isoformat() if r.started_at else "", "completed_at": r.completed_at.isoformat() if r.completed_at else "", "status": r.status, "output_preview": _preview_text(getattr(r, "output", "")), } def cmd_list(args): db = SessionLocal() try: q = db.query(ScheduledTask) if args.status: q = q.filter(ScheduledTask.status == args.status) q = q.order_by(ScheduledTask.next_run.asc().nulls_last()).limit(args.limit) emit([_serialize_task(t) for t in q.all()], args) finally: db.close() def cmd_show(args): db = SessionLocal() try: t = db.get(ScheduledTask, args.id) if not t: fail(f"no task with id {args.id!r}") out = _serialize_task(t) # Include full prompt + recent runs for detail view out["prompt_full"] = t.prompt or "" out["endpoint_url"] = t.endpoint_url or "" out["session_id"] = t.session_id or "" out["webhook_token"] = "***" if t.webhook_token else "" runs = db.query(TaskRun).filter(TaskRun.task_id == t.id).order_by( TaskRun.started_at.desc() ).limit(5).all() out["recent_runs"] = [_serialize_run(r) for r in runs] emit(out, args) finally: db.close() def cmd_pause(args): _set_status(args, "paused") def cmd_resume(args): _set_status(args, "active") def _set_status(args, new_status: str): db = SessionLocal() try: t = db.get(ScheduledTask, args.id) if not t: fail(f"no task with id {args.id!r}") t.status = new_status db.commit() db.refresh(t) emit({"ok": True, "id": t.id, "status": t.status}, args) finally: db.close() def cmd_runs(args): db = SessionLocal() try: q = db.query(TaskRun) if args.task: q = q.filter(TaskRun.task_id == args.task) q = q.order_by(TaskRun.started_at.desc()).limit(args.limit) emit([_serialize_run(r) for r in q.all()], 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-tasks", parents=[common]) sub = p.add_subparsers(dest="cmd", required=True) pl = sub.add_parser("list", parents=[common]) pl.add_argument("--status", choices=["active", "paused", "completed"]) 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) pp = sub.add_parser("pause", parents=[common]) pp.add_argument("id") pp.set_defaults(func=cmd_pause) pr = sub.add_parser("resume", parents=[common]) pr.add_argument("id") pr.set_defaults(func=cmd_resume) prn = sub.add_parser("runs", parents=[common]) prn.add_argument("--task", help="filter by task id") prn.add_argument("--limit", type=int, default=20) prn.set_defaults(func=cmd_runs) return p if __name__ == "__main__": sys.exit(run(_build_parser()))