platforms/hermes/skills/autonomous-ai-agents/hermes-cron-local-script-notify/SKILL.md
Create lightweight Hermes cron jobs that offload work into a local pre-run script, avoid chat-context overhead, and send macOS notifications for success/failure.
npx skillsauth add codingsamss/ai-dotfiles hermes-cron-local-script-notifyInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Use this when the user wants a scheduled task that should be as lightweight and deterministic as possible, especially for local keepalive pings, one-shot Codex CLI calls, or simple background automations.
Do the real work in a cron script, and make the cron prompt trivial.
This keeps the autonomous run lighter because:
skills can be cleared (skills=[]) if not needed~/.hermes/scripts/cronjob(action='create' or 'update', script='...')skills=[]If the goal is just "poke Codex every few hours" or "refresh a 5h window", don't load a Codex skill and don't make the cron agent reason about how to call Codex. Instead, call Codex directly from a local script.
codex exec can run outside a git repo with --skip-git-repo-checkgit init is optional and usually unnecessary--ephemeral if you want a lighter one-shot that does not persist session filescronjob(action='run') a newly created recurring job, next_run_at may temporarily reflect the manual run rather than the next clean scheduled slot. If the user wants a clean schedule display, avoid immediate test-runs and test the script directly instead.Save as ~/.hermes/scripts/codex_keepalive_notify.py
#!/usr/bin/env python3
import shutil
import subprocess
import tempfile
from pathlib import Path
PROMPT = "只回复:你好,不要输出任何其他内容。"
TITLE = "Codex Cron"
SUCCESS_MSG = "Codex keepalive 成功:你好"
def shorten(text: str, limit: int = 120) -> str:
text = " ".join(text.replace("\r", " ").replace("\n", " ").split())
return text if len(text) <= limit else text[: limit - 1] + "…"
def notify(message: str) -> bool:
notifier = shutil.which("terminal-notifier")
if notifier:
res = subprocess.run([notifier, "-title", TITLE, "-message", message], capture_output=True, text=True)
if res.returncode == 0:
return True
osa = shutil.which("osascript")
if osa:
safe_message = message.replace('\\', '\\\\').replace('"', '\\"')
safe_title = TITLE.replace('\\', '\\\\').replace('"', '\\"')
script = f'display notification "{safe_message}" with title "{safe_title}"'
res = subprocess.run([osa, "-e", script], capture_output=True, text=True)
if res.returncode == 0:
return True
return False
def main() -> int:
with tempfile.TemporaryDirectory(prefix="codex-keepalive-") as tmp:
tmp_path = Path(tmp)
out_file = tmp_path / "last_message.txt"
cmd = [
"codex", "exec",
"--skip-git-repo-check",
"--ephemeral",
"--color", "never",
"--cd", str(tmp_path),
"--output-last-message", str(out_file),
PROMPT,
]
res = subprocess.run(cmd, capture_output=True, text=True)
msg = out_file.read_text(encoding="utf-8", errors="replace").strip() if out_file.exists() else ""
if res.returncode == 0 and msg == "你好":
print("OK 你好" if notify(SUCCESS_MSG) else "OK 你好 | notify-unavailable")
return 0
combined = "\n".join(part for part in [msg, res.stderr, res.stdout] if part).strip()
reason = shorten(combined or f"exit {res.returncode}")
notify(f"Codex keepalive 失败:{reason}")
print(f"ERR {reason}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
Validate it first:
terminal(command="python3 -m py_compile ~/.hermes/scripts/codex_keepalive_notify.py")
terminal(command="python3 ~/.hermes/scripts/codex_keepalive_notify.py", timeout=240)
cronjob(
action="update",
job_id="<job_id>",
schedule="30 8,13,18,23 * * *",
deliver="origin",
skills=[],
script="codex_keepalive_notify.py",
prompt="预运行脚本已经完成工作。只把脚本 stdout 的最后一行原样输出,不要添加解释。"
)
Creating/updating the cron job is not sufficient by itself. Hermes cron depends on the Hermes gateway scheduler.
Recommended checks:
hermes gateway install on macOS user sessionshermes gateway statushermes cron statushermes cron status as the most authoritative quick check for whether jobs will actually fire.launchctl list | grep 'ai.hermes.gateway'hermes status --all (look for Gateway Service loaded/not loaded)Important macOS behavior:
hermes gateway install creates a LaunchAgent at ~/Library/LaunchAgents/ai.hermes.gateway.plist~/.hermes/logs/gateway.logCheck all three:
cronjob(action='list') shows sane values for:
next_run_atlast_run_atlast_statuslast_delivery_errorIf you use terminal-notifier without extra flags, the notification icon is the terminal-notifier app icon, not the title text and not the app you are conceptually automating.
In Homebrew terminal-notifier 2.0.0 this can look like an orange icon with a white starburst/flower.
To change it, use one of:
-sender com.apple.Terminal for Terminal icon-sender <bundle-id> for another app icon-appIcon <png-or-url> for a custom iconDo not assume a title like Codex Cron will change the icon automatically.
Hermes runs cron scripts with subprocess.run(..., capture_output=True, timeout=_SCRIPT_TIMEOUT). If the script times out, the cron prompt only receives a generic error like:
Script timed out after 120s: /Users/<user>/.hermes/scripts/your_script.py
Important consequence:
Use this when the timed-out script itself is designed to write its meaningful result to a file before exiting or hanging.
For the Codex keepalive pattern above:
tempfile.TemporaryDirectory(prefix="codex-keepalive-")out_file = tmp_path / "last_message.txt"/var/folders/.../T/last_message.txt or the known prefix~/.hermes/logs/gateway.log or the cron session timestampIf the child command (for example codex exec --output-last-message <file>) already wrote its last message file before the parent Python script timed out, that file can still survive briefly in the temp directory even though Hermes only reports the generic timeout string.
Do not assume every leftover temp artifact belongs to the current failed run.
Use this checklist:
last_run_at, current session timestamp, or nearby gateway/agent log timestamps)Script timed out after 120s: ...) instead of reusing an old success payloadThis avoids incorrectly replaying a previous run's last_message.txt when the current run produced no trustworthy recoverable stdout.
When investigating cron behavior after the fact, check:
~/.hermes/cron/output/<job_id>/ for archived prompt/response markdown from prior runs~/.hermes/sessions/session_cron_<job_id>_*.json for the full cron conversation history~/.hermes/logs/agent.log and ~/.hermes/logs/gateway.log for nearby timestamps and scheduler activityThese are especially helpful for correlating whether a recovered temp artifact is from the same run.
run if the user wants a clean next_run_atskills=[] is important when the point is minimal overheadterminal-notifier is used without -sender/-appIcon, the icon will be terminal-notifier's own app icon, not the conceptual app being automated; use -sender com.apple.Terminal if the user wants a Terminal-looking notificationGATEWAY_ALLOW_ALL_USERS=true unless the user explicitly wants open access; for cron-only local use it is unnecessarydevelopment
Query Midea MX / 美信 local message cache through the MX local HTTP query service from Codex. Use when the user asks to read MX sessions, search chat history, search messages globally or inside a group/session, list recent messages, or page message history. This is read-only and does not require send authorization. Never fall back to reading SQLite or app cache files directly.
development
Safely search MX users or groups and send Midea MX / 美信 IM messages from Codex. Use when the user asks to notify someone, send a message to a person or group, use a configured group alias, @ users, @ all, or send MX file/image messages. Read lookups need no extra authorization; every live send needs explicit user authorization for that exact target and message.
tools
MX channel output rules. Always active in MX conversations.
tools
Use the company WorkSpace `ws` CLI reliably as a delegated coding agent from Codex. Trigger when the user wants Codex to command `ws`, WorkSpace CLI, or the company opencode-derived coding tool to generate code, inspect a repo, run a bounded implementation task, or use a requested WorkSpace model while Codex reviews the output.