stage-8: cron scheduler with APScheduler
Scheduler class, cron/jobs.json, Discord /cron commands, CLI cron subcommand, job lifecycle management. 88 new tests (281 total). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
142
cli.py
142
cli.py
@@ -292,6 +292,119 @@ def cmd_send(args):
|
||||
print(response)
|
||||
|
||||
|
||||
def cmd_cron(args):
|
||||
"""Handle cron subcommand."""
|
||||
if args.cron_action == "list":
|
||||
_cron_list()
|
||||
elif args.cron_action == "run":
|
||||
_cron_run(args.name)
|
||||
elif args.cron_action == "add":
|
||||
tools = [t.strip() for t in args.tools.split(",")] if args.tools else []
|
||||
if args.prompt == "-":
|
||||
prompt = sys.stdin.read().strip()
|
||||
else:
|
||||
prompt = args.prompt
|
||||
_cron_add(args.name, args.expression, args.channel, prompt,
|
||||
args.model, tools)
|
||||
elif args.cron_action == "remove":
|
||||
_cron_remove(args.name)
|
||||
elif args.cron_action == "enable":
|
||||
_cron_enable(args.name)
|
||||
elif args.cron_action == "disable":
|
||||
_cron_disable(args.name)
|
||||
|
||||
|
||||
def _cron_list():
|
||||
"""List scheduled jobs in tabular format."""
|
||||
from src.scheduler import Scheduler
|
||||
s = Scheduler()
|
||||
s._jobs = s._load_jobs()
|
||||
jobs = s.list_jobs()
|
||||
if not jobs:
|
||||
print("No scheduled jobs.")
|
||||
return
|
||||
|
||||
print(f"{'Name':<24} {'Cron':<16} {'Channel':<10} {'Model':<8} {'Enabled':<8} {'Last Status':<12} {'Next Run'}")
|
||||
print(f"{'-'*24} {'-'*16} {'-'*10} {'-'*8} {'-'*8} {'-'*12} {'-'*20}")
|
||||
|
||||
for job in jobs:
|
||||
name = job.get("name", "?")
|
||||
cron = job.get("cron", "?")
|
||||
channel = job.get("channel", "?")
|
||||
model = job.get("model", "?")
|
||||
enabled = "yes" if job.get("enabled") else "no"
|
||||
last_status = job.get("last_status") or "-"
|
||||
next_run = job.get("next_run") or "-"
|
||||
if next_run != "-" and len(next_run) > 19:
|
||||
next_run = next_run[:19].replace("T", " ")
|
||||
print(f"{name:<24} {cron:<16} {channel:<10} {model:<8} {enabled:<8} {last_status:<12} {next_run}")
|
||||
|
||||
|
||||
def _cron_run(name: str):
|
||||
"""Force-run a job and print output."""
|
||||
import asyncio
|
||||
from src.scheduler import Scheduler
|
||||
|
||||
async def _run():
|
||||
s = Scheduler()
|
||||
s._jobs = s._load_jobs()
|
||||
return await s.run_job(name)
|
||||
|
||||
try:
|
||||
result = asyncio.run(_run())
|
||||
print(result)
|
||||
except KeyError as exc:
|
||||
print(f"Error: {exc}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def _cron_add(name: str, cron: str, channel: str, prompt: str,
|
||||
model: str, tools: list[str]):
|
||||
"""Add a new cron job."""
|
||||
from src.scheduler import Scheduler
|
||||
s = Scheduler()
|
||||
s._jobs = s._load_jobs()
|
||||
try:
|
||||
job = s.add_job(name, cron, channel, prompt, model, tools or None)
|
||||
print(f"Job '{job['name']}' added (cron: {job['cron']}, channel: {job['channel']}, model: {job['model']})")
|
||||
except ValueError as exc:
|
||||
print(f"Error: {exc}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def _cron_remove(name: str):
|
||||
"""Remove a cron job."""
|
||||
from src.scheduler import Scheduler
|
||||
s = Scheduler()
|
||||
s._jobs = s._load_jobs()
|
||||
if s.remove_job(name):
|
||||
print(f"Job '{name}' removed.")
|
||||
else:
|
||||
print(f"Job '{name}' not found.")
|
||||
|
||||
|
||||
def _cron_enable(name: str):
|
||||
"""Enable a cron job."""
|
||||
from src.scheduler import Scheduler
|
||||
s = Scheduler()
|
||||
s._jobs = s._load_jobs()
|
||||
if s.enable_job(name):
|
||||
print(f"Job '{name}' enabled.")
|
||||
else:
|
||||
print(f"Job '{name}' not found.")
|
||||
|
||||
|
||||
def _cron_disable(name: str):
|
||||
"""Disable a cron job."""
|
||||
from src.scheduler import Scheduler
|
||||
s = Scheduler()
|
||||
s._jobs = s._load_jobs()
|
||||
if s.disable_job(name):
|
||||
print(f"Job '{name}' disabled.")
|
||||
else:
|
||||
print(f"Job '{name}' not found.")
|
||||
|
||||
|
||||
def cmd_secrets(args):
|
||||
"""Handle secrets subcommand."""
|
||||
if args.secrets_action == "set":
|
||||
@@ -396,6 +509,32 @@ def main():
|
||||
|
||||
secrets_sub.add_parser("test", help="Check required secrets")
|
||||
|
||||
# cron
|
||||
cron_parser = sub.add_parser("cron", help="Manage scheduled jobs")
|
||||
cron_sub = cron_parser.add_subparsers(dest="cron_action")
|
||||
|
||||
cron_sub.add_parser("list", help="List scheduled jobs")
|
||||
|
||||
cron_run_p = cron_sub.add_parser("run", help="Force-run a job")
|
||||
cron_run_p.add_argument("name", help="Job name")
|
||||
|
||||
cron_add_p = cron_sub.add_parser("add", help="Add a scheduled job")
|
||||
cron_add_p.add_argument("name", help="Job name")
|
||||
cron_add_p.add_argument("expression", help="Cron expression (e.g. '30 6 * * *')")
|
||||
cron_add_p.add_argument("--channel", required=True, help="Channel alias")
|
||||
cron_add_p.add_argument("--prompt", required=True, help="Prompt text (use '-' for stdin)")
|
||||
cron_add_p.add_argument("--model", default="sonnet", help="Model (default: sonnet)")
|
||||
cron_add_p.add_argument("--tools", default=None, help="Comma-separated allowed tools")
|
||||
|
||||
cron_remove_p = cron_sub.add_parser("remove", help="Remove a job")
|
||||
cron_remove_p.add_argument("name", help="Job name")
|
||||
|
||||
cron_enable_p = cron_sub.add_parser("enable", help="Enable a job")
|
||||
cron_enable_p.add_argument("name", help="Job name")
|
||||
|
||||
cron_disable_p = cron_sub.add_parser("disable", help="Disable a job")
|
||||
cron_disable_p.add_argument("name", help="Job name")
|
||||
|
||||
# Parse and dispatch
|
||||
args = parser.parse_args()
|
||||
|
||||
@@ -415,6 +554,9 @@ def main():
|
||||
cmd_channel(a) if a.channel_action else (channel_parser.print_help() or sys.exit(0))
|
||||
),
|
||||
"send": cmd_send,
|
||||
"cron": lambda a: (
|
||||
cmd_cron(a) if a.cron_action else (cron_parser.print_help() or sys.exit(0))
|
||||
),
|
||||
"secrets": lambda a: (
|
||||
cmd_secrets(a) if a.secrets_action else (secrets_parser.print_help() or sys.exit(0))
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user