From 02586dbed0e30c5ed43fe4b2ae4a2503c5b58ad8 Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Wed, 2 Sep 2026 22:58:35 -0700 Subject: [PATCH] refactor(hermes_cli): cron incidents rows table, debug share flow tightening, copilot env-var loop invert --- hermes_cli/copilot_auth.py | 20 +++++++++++--------- hermes_cli/cron.py | 26 ++++++++++++++------------ hermes_cli/debug.py | 23 ++++++++--------------- 3 files changed, 33 insertions(+), 36 deletions(-) diff --git a/hermes_cli/copilot_auth.py b/hermes_cli/copilot_auth.py index cecc6451c5..18d8f0ba37 100644 --- a/hermes_cli/copilot_auth.py +++ b/hermes_cli/copilot_auth.py @@ -66,13 +66,13 @@ def resolve_copilot_token() -> tuple[str, str]: any_env_var_set = False for env_var in COPILOT_ENV_VARS: val = os.getenv(env_var, "").strip() - if val: - any_env_var_set = True - valid, msg = validate_copilot_token(val) - if not valid: - logger.warning("Token from %s is not supported: %s", env_var, msg) - continue + if not val: + continue + any_env_var_set = True + valid, msg = validate_copilot_token(val) + if valid: return val, env_var + logger.warning("Token from %s is not supported: %s", env_var, msg) # Fall back to gh auth token ONLY when no Copilot env var was explicitly set: an exported # GITHUB_TOKEN (even an unsupported classic PAT) means the user intends *that* token, not @@ -207,7 +207,8 @@ def copilot_device_code_login( print(" ✗ GitHub did not return a device code.") return None - print(f"\n Open this URL in your browser: {verification_uri}\n Enter this code: {user_code}\n") + print(f"\n Open this URL in your browser: {verification_uri}\n" + f" Enter this code: {user_code}\n") print(" Waiting for authorization...", end="", flush=True) deadline = time.monotonic() + timeout_seconds @@ -243,7 +244,8 @@ def copilot_device_code_login( print(".", end="", flush=True) continue if error: - print("\n" + _DEVICE_CODE_TERMINAL_ERRORS.get(error, f" ✗ Authorization failed: {error}")) + print("\n" + _DEVICE_CODE_TERMINAL_ERRORS.get(error, + f" ✗ Authorization failed: {error}")) return None print("\n ✗ Timed out waiting for authorization.") @@ -349,7 +351,7 @@ def _jwt_disk_path() -> Optional[Path]: def _with_jwt_store(verb: str, op): - """Run ``op(path, store_or_None)`` against the disk store; any failure is logged, never raised.""" + """Run ``op(path, store_or_None)`` against the disk store; failures are logged, never raised.""" path = _jwt_disk_path() if not path: return None diff --git a/hermes_cli/cron.py b/hermes_cli/cron.py index 60d7ddf3df..760cabbce6 100644 --- a/hermes_cli/cron.py +++ b/hermes_cli/cron.py @@ -67,7 +67,8 @@ def _builtin_gateway_liveness() -> Optional[bool]: # gateway process it short-circuits to True so the in-gateway cron tool never # emits a false "gateway not running" (find_gateway_pids can transiently miss the # gateway just after a restart). - with contextlib.suppress(Exception): # a crashing probe is "unknown" — let the pid scan decide + # A crashing lock probe is "unknown", not "dead" — let the pid scan decide. + with contextlib.suppress(Exception): from gateway.status import is_gateway_runtime_lock_active if is_gateway_runtime_lock_active(): return True @@ -212,7 +213,8 @@ def _job_rows(job: Dict[str, Any]) -> List[tuple[str, str]]: optional = [ ("Skills", ", ".join(skills) if skills else ""), ("Script", job.get("script")), - ("Monitor", f"{monitor_source} (agent runs only on output change)" if monitor_source else ""), + ("Monitor", f"{monitor_source} (agent runs only on output change)" if monitor_source + else ""), ("Changed", mon_state.get("last_changed_at") if monitor_source else ""), ("Mode", color("no-agent", Colors.DIM) + " (script stdout delivered directly)" if job.get("no_agent") else ""), @@ -318,17 +320,17 @@ def cron_incidents(args) -> int: _print_banner("Cron Failure Incidents") for inc in incidents: state_display = color(inc["state"], _INCIDENT_STATE_COLORS.get(inc["state"], Colors.DIM)) - print(f" {color(inc['id'], Colors.YELLOW)} {state_display}") - print(f" Job: {inc['job_id']}") - print(f" Type: {inc.get('failure_type', 'unknown')}") - print(f" First seen: {inc.get('first_seen_at', '?')}") - print(f" Last seen: {inc.get('last_seen_at', '?')}") error_text = re.sub(r"\s+", " ", inc.get("error") or "").strip() if len(error_text) > 160: error_text = error_text[:157].rstrip() + "..." - print(f" Error: {error_text}") - if inc.get("output_file"): - print(f" Output: {inc['output_file']}") + rows = [("Job", inc["job_id"]), ("Type", inc.get("failure_type", "unknown")), + ("First seen", inc.get("first_seen_at", "?")), + ("Last seen", inc.get("last_seen_at", "?")), ("Error", error_text), + ("Output", inc.get("output_file"))] + print(f" {color(inc['id'], Colors.YELLOW)} {state_display}") + for label, value in rows: + if label != "Output" or value: + print(f" {label + ':':<12}{value}") print() print(color(f" {len(incidents)} incident(s) | ack one with: hermes cron incidents ack ", Colors.DIM)) @@ -566,8 +568,8 @@ def cron_doctor() -> int: if not findings: print(color("✓ Cron doctor found no issues", Colors.GREEN)) - print(color(f" Checked {len(jobs)} active job(s)." if jobs else " No active jobs configured.", - Colors.DIM)) + summary = f" Checked {len(jobs)} active job(s)." if jobs else " No active jobs configured." + print(color(summary, Colors.DIM)) return 0 issue_count = sum(len(issues) for _, issues in findings) diff --git a/hermes_cli/debug.py b/hermes_cli/debug.py index fa28e5cbd0..436e10614f 100644 --- a/hermes_cli/debug.py +++ b/hermes_cli/debug.py @@ -531,21 +531,18 @@ def _confirm_upload(args) -> bool: if bool(getattr(args, "yes", False)): return True if not sys.stdin.isatty(): - print( - "ERROR: Non-interactive mode requires --yes to confirm upload.\n" - " This prevents accidental exposure of personal data.\n" - " Use --local to view the report without uploading.", - file=sys.stderr, - ) + print("ERROR: Non-interactive mode requires --yes to confirm upload.\n" + " This prevents accidental exposure of personal data.\n" + " Use --local to view the report without uploading.", file=sys.stderr) sys.exit(1) try: answer = input("Upload debug report? [y/N] ").strip().lower() except (EOFError, KeyboardInterrupt): answer = "" - if answer not in ("y", "yes"): - print("Aborted.") - return False - return True + if answer in ("y", "yes"): + return True + print("Aborted.") + return False def run_debug_share(args): @@ -574,7 +571,6 @@ def run_debug_share(args): if not _confirm_upload(args): return print("Collecting debug report...\nUploading...") - try: result = build_debug_share(log_lines=log_lines, expiry=expiry, redact=redact) except RuntimeError as exc: @@ -618,15 +614,12 @@ def _run_debug_share_nous(args, *, log_lines: int, redact: bool) -> None: print("⚠️ --no-redact is set: secrets in your logs will NOT be redacted before upload.\n") print("Collecting debug report...") _best_effort_sweep_expired_pastes() - bundle = collect_share_bundle(log_lines=log_lines, redact=redact) if redact: logger.info("hermes debug share --nous: applied force-mode redaction before upload") - blob = build_nous_bundle(bundle, redact=redact) - print("Uploading to Nous diagnostics storage...") try: - res = share_to_nous(blob) + res = share_to_nous(build_nous_bundle(bundle, redact=redact)) except Exception as exc: print( f"\nNous upload failed: {exc}\n"