mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-09 08:21:50 +00:00
Phase 2 of the god-file decomposition plan. main()'s argparse tree is 179 inline add_parser calls in one 3,297-line function. This establishes the hermes_cli/subcommands/ package and extracts the first group (cron) as the proof-of-pattern: - hermes_cli/subcommands/_shared.py: shared parser helpers (add_accept_hooks_flag), re-exported from main.py for backwards compat. - hermes_cli/subcommands/cron.py: build_cron_parser(subparsers, cmd_cron=...). Handler injected so the module never imports main (cycle avoidance). - main()'s ~155-line inline cron block becomes one build_cron_parser() call. Behavior-neutral: 'hermes cron create --help' output is byte-identical to origin/main. main() 3297 -> 3143 LOC. Validation: tests/hermes_cli/ 6466 passed / 0 failed under per-file process isolation; new test_subcommands_cron.py covers subactions, aliases, options, no-agent tristate, injected dispatch, and --accept-hooks.
171 lines
6.4 KiB
Python
171 lines
6.4 KiB
Python
"""``hermes cron`` subcommand parser.
|
|
|
|
Extracted verbatim from ``hermes_cli/main.py:main()`` — same arguments, same
|
|
``func=cmd_cron`` dispatch. The handler is injected so this module does not
|
|
import ``main`` (cycle avoidance).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Callable
|
|
|
|
from hermes_cli.subcommands._shared import add_accept_hooks_flag
|
|
|
|
|
|
def build_cron_parser(subparsers, *, cmd_cron: Callable) -> None:
|
|
"""Attach the ``cron`` subcommand (and its sub-actions) to ``subparsers``."""
|
|
cron_parser = subparsers.add_parser(
|
|
"cron", help="Cron job management", description="Manage scheduled tasks"
|
|
)
|
|
cron_subparsers = cron_parser.add_subparsers(dest="cron_command")
|
|
|
|
# cron list
|
|
cron_list = cron_subparsers.add_parser("list", help="List scheduled jobs")
|
|
cron_list.add_argument("--all", action="store_true", help="Include disabled jobs")
|
|
|
|
# cron create/add
|
|
cron_create = cron_subparsers.add_parser(
|
|
"create", aliases=["add"], help="Create a scheduled job"
|
|
)
|
|
cron_create.add_argument(
|
|
"schedule", help="Schedule like '30m', 'every 2h', or '0 9 * * *'"
|
|
)
|
|
cron_create.add_argument(
|
|
"prompt", nargs="?", help="Optional self-contained prompt or task instruction"
|
|
)
|
|
cron_create.add_argument("--name", help="Optional human-friendly job name")
|
|
cron_create.add_argument(
|
|
"--deliver",
|
|
help="Delivery target: origin, local, telegram, discord, signal, or platform:chat_id",
|
|
)
|
|
cron_create.add_argument("--repeat", type=int, help="Optional repeat count")
|
|
cron_create.add_argument(
|
|
"--skill",
|
|
dest="skills",
|
|
action="append",
|
|
help="Attach a skill. Repeat to add multiple skills.",
|
|
)
|
|
cron_create.add_argument(
|
|
"--script",
|
|
help=(
|
|
"Path to a script under ~/.hermes/scripts/. Default mode: "
|
|
"script stdout is injected into the agent's prompt each run. "
|
|
"With --no-agent: the script IS the job and its stdout is "
|
|
"delivered verbatim. .sh/.bash files run via bash, everything "
|
|
"else via Python."
|
|
),
|
|
)
|
|
cron_create.add_argument(
|
|
"--no-agent",
|
|
dest="no_agent",
|
|
action="store_true",
|
|
default=False,
|
|
help=(
|
|
"Skip the LLM entirely — run --script on schedule and deliver "
|
|
"its stdout directly. Empty stdout = silent. Classic watchdog "
|
|
"pattern (memory alerts, disk alerts, CI pings)."
|
|
),
|
|
)
|
|
cron_create.add_argument(
|
|
"--workdir",
|
|
help="Absolute path for the job to run from. Injects AGENTS.md / CLAUDE.md / .cursorrules from that directory and uses it as the cwd for terminal/file/code_exec tools. Omit to preserve old behaviour (no project context files).",
|
|
)
|
|
cron_create.add_argument(
|
|
"--profile",
|
|
help="Hermes profile name to run the job under. Use 'default' for the root profile. Named profiles must already exist. Omit to preserve the scheduler's existing profile.",
|
|
)
|
|
|
|
# cron edit
|
|
cron_edit = cron_subparsers.add_parser(
|
|
"edit", help="Edit an existing scheduled job"
|
|
)
|
|
cron_edit.add_argument("job_id", help="Job ID to edit")
|
|
cron_edit.add_argument("--schedule", help="New schedule")
|
|
cron_edit.add_argument("--prompt", help="New prompt/task instruction")
|
|
cron_edit.add_argument("--name", help="New job name")
|
|
cron_edit.add_argument("--deliver", help="New delivery target")
|
|
cron_edit.add_argument("--repeat", type=int, help="New repeat count")
|
|
cron_edit.add_argument(
|
|
"--skill",
|
|
dest="skills",
|
|
action="append",
|
|
help="Replace the job's skills with this set. Repeat to attach multiple skills.",
|
|
)
|
|
cron_edit.add_argument(
|
|
"--add-skill",
|
|
dest="add_skills",
|
|
action="append",
|
|
help="Append a skill without replacing the existing list. Repeatable.",
|
|
)
|
|
cron_edit.add_argument(
|
|
"--remove-skill",
|
|
dest="remove_skills",
|
|
action="append",
|
|
help="Remove a specific attached skill. Repeatable.",
|
|
)
|
|
cron_edit.add_argument(
|
|
"--clear-skills",
|
|
action="store_true",
|
|
help="Remove all attached skills from the job",
|
|
)
|
|
cron_edit.add_argument(
|
|
"--script",
|
|
help=(
|
|
"Path to a script under ~/.hermes/scripts/. Pass empty string to clear. "
|
|
"With --no-agent the script IS the job; otherwise its stdout is "
|
|
"injected into the agent's prompt each run."
|
|
),
|
|
)
|
|
cron_edit.add_argument(
|
|
"--no-agent",
|
|
dest="no_agent",
|
|
action="store_const",
|
|
const=True,
|
|
default=None,
|
|
help=(
|
|
"Enable no-agent mode on this job (requires --script or an "
|
|
"existing script on the job)."
|
|
),
|
|
)
|
|
cron_edit.add_argument(
|
|
"--agent",
|
|
dest="no_agent",
|
|
action="store_const",
|
|
const=False,
|
|
help="Disable no-agent mode on this job (reverts to LLM-driven execution).",
|
|
)
|
|
cron_edit.add_argument(
|
|
"--workdir",
|
|
help="Absolute path for the job to run from (injects AGENTS.md etc. and sets terminal cwd). Pass empty string to clear.",
|
|
)
|
|
cron_edit.add_argument(
|
|
"--profile",
|
|
help="Hermes profile name to run the job under. Use 'default' for the root profile. Pass empty string to clear.",
|
|
)
|
|
|
|
# lifecycle actions
|
|
cron_pause = cron_subparsers.add_parser("pause", help="Pause a scheduled job")
|
|
cron_pause.add_argument("job_id", help="Job ID to pause")
|
|
|
|
cron_resume = cron_subparsers.add_parser("resume", help="Resume a paused job")
|
|
cron_resume.add_argument("job_id", help="Job ID to resume")
|
|
|
|
cron_run = cron_subparsers.add_parser(
|
|
"run", help="Run a job on the next scheduler tick"
|
|
)
|
|
cron_run.add_argument("job_id", help="Job ID to trigger")
|
|
add_accept_hooks_flag(cron_run)
|
|
|
|
cron_remove = cron_subparsers.add_parser(
|
|
"remove", aliases=["rm", "delete"], help="Remove a scheduled job"
|
|
)
|
|
cron_remove.add_argument("job_id", help="Job ID to remove")
|
|
|
|
# cron status
|
|
cron_subparsers.add_parser("status", help="Check if cron scheduler is running")
|
|
|
|
# cron tick (mostly for debugging)
|
|
cron_tick = cron_subparsers.add_parser("tick", help="Run due jobs once and exit")
|
|
add_accept_hooks_flag(cron_tick)
|
|
add_accept_hooks_flag(cron_parser)
|
|
cron_parser.set_defaults(func=cmd_cron)
|