"""`python -m lib_py.delegation.cli` — the router the shell helpers call. Deliberately thin: every subcommand delegates to the existing `main()` of the module that already owned that behaviour, so exit codes, stdout shape and side effects cannot drift between the shell path and the script path. The one piece of real logic here is `ensure-daemon`, which reproduces the shell helper's stderr wording and exit status for a missing monitor script. """ from __future__ import annotations import argparse import logging import sys from typing import List, Optional from lib_py.delegation import daemon, mqtt_common, publish_event, registry logger = logging.getLogger("delegate_job.cli") def _build_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser( prog="lib_py.delegation.cli", description="delegation helpers invoked by lib.sh", ) sub = parser.add_subparsers(dest="command", required=True) p_submit = sub.add_parser("submit", help="register a job; prints the job id") # Issue #36: lib.sh passes the prompt by file (one argv string is capped # at 131,072 bytes); --prompt keeps working. submit_prompt = p_submit.add_mutually_exclusive_group(required=True) submit_prompt.add_argument("--prompt") submit_prompt.add_argument("--prompt-file") p_submit.add_argument("--agent", default="claude-code") p_submit.add_argument("--agent-session", default="herdr:claude") p_submit.add_argument("--registry-dir", default=None) p_pub = sub.add_parser("publish", help="publish a job lifecycle event") p_pub.add_argument("--job", required=True) p_pub.add_argument("--event", default="progress", choices=publish_event.VALID_EVENTS) p_pub.add_argument("--detail", default="") p_pub.add_argument("--heartbeat", type=int, default=None, metavar="N", help="start the automatic heartbeat timer (Issue #18)") p_pub.add_argument("--data", default=None) p_pub.add_argument("--retained", action="store_true") p_pub.add_argument("--registry-dir", default=None) p_pub.add_argument("--attempts", type=int, default=3) p_pub.add_argument("--expect-dispatch-token", default=None) p_pub.add_argument("--attempt-id", type=int, default=None) p_pub.add_argument("-v", "--verbose", action="store_true") p_daemon = sub.add_parser("ensure-daemon", help="start the subscriber daemon if absent") p_daemon.add_argument("--job", required=True) p_daemon.add_argument("--workdir", default=None) return parser def main(argv: Optional[List[str]] = None) -> int: if not mqtt_common.load_env_or_report(): return 1 args = _build_parser().parse_args(argv) if args.command == "submit": prompt_arg = (["--prompt-file", args.prompt_file] if args.prompt_file is not None else ["--prompt", args.prompt]) forward = ["register", *prompt_arg, "--agent", args.agent, "--agent-session", args.agent_session] if args.registry_dir: forward = ["--registry-dir", args.registry_dir] + forward return registry.main(forward) if args.command == "publish": forward = ["--job", args.job, "--event", args.event, "--detail", args.detail, "--attempts", str(args.attempts)] if args.data is not None: forward += ["--data", args.data] if args.retained: forward += ["--retained"] if args.registry_dir: forward += ["--registry-dir", args.registry_dir] if args.expect_dispatch_token: forward += ["--expect-dispatch-token", args.expect_dispatch_token] if args.attempt_id is not None: forward += ["--attempt-id", str(args.attempt_id)] if getattr(args, "heartbeat", None) is not None: forward += ["--heartbeat", str(args.heartbeat)] if args.verbose: forward += ["--verbose"] return publish_event.main(forward) if args.command == "ensure-daemon": try: pids = daemon.ensure_subscriber_daemon(args.job, args.workdir) except FileNotFoundError as exc: # Byte-identical to the legacy shell helper's failure path. print("ERROR: monitor script not found: {}".format(exc.args[0]), file=sys.stderr) return 1 if not pids and daemon.daemon_disabled(): print("subscriber daemon not started ({}=1)".format(daemon.NO_DAEMON_ENV), file=sys.stderr) for pid in pids: print(pid) return 0 return 1 if __name__ == "__main__": sys.exit(main())