@coldxiangyu/hermes-cron-doctor
v0.2.0
Published
Local health-check CLI for Hermes cron jobs (~/.hermes/cron/jobs.json + output/).
Downloads
66
Maintainers
Readme
hermes-cron-doctor
Local health-check CLI for Hermes cron jobs. Reads
~/.hermes/cron/jobs.jsonand the artifact tree under~/.hermes/cron/output/, surfaces each job's schedule, last/next run, last status, and most recent artifact — both as a one-line terminal table and as a single-file offline HTML report.
Why
Hermes runs lots of background cron jobs (briefings, watchers, daily digests). When something quietly breaks — a quota error, a paused job, a stale artifact — you usually only notice when the downstream Feishu doc is empty. hermes-cron-doctor gives you a single command (and a shareable HTML page) that summarises every job's recent health, so the silent failures aren't silent anymore.
It's intentionally small and Hermes-shaped: it knows the actual { "jobs": [...] } schema and the <output>/<job-id>/<timestamp>.md artifact layout, so you don't need to teach it anything to run.
Install
npm install -g @coldxiangyu/hermes-cron-doctorOr run from a clone:
git clone https://github.com/coldxiangyu163/hermes-cron-doctor.git
cd hermes-cron-doctor
npm install
node bin/cli.js statusRequires Node.js ≥ 18. Runtime dependencies: commander, ejs.
Usage
status — terminal one-liner per job
hermes-cron-doctor status
hermes-cron-doctor status --jobs ~/.hermes/cron/jobs.json --output-dir ~/.hermes/cron/output
hermes-cron-doctor status --jsonSample output:
# hermes-cron-doctor status
jobs: /Users/you/.hermes/cron/jobs.json
output: /Users/you/.hermes/cron/output
count: 4
NAME KIND SCHEDULE LAST_RUN NEXT_RUN LAST_STATUS ARTIFACT
---------------------- -------- ------------------------ ------------------------- ------------------------- ----------- ----------------------
daily-morning-briefing cron 30 7 * * * 2026-05-10T07:30:12+08:00 2026-05-11T07:30:00+08:00 ok 2026-05-10_07-30-12.md
x-ai-watch-hourly interval every 180m 2026-05-10T08:20:44+08:00 2026-05-10T11:20:44+08:00 error 2026-05-10_08-20-44.md
release-launch-ping once once at 2026-05-12T09:0… - 2026-05-12T09:00:00+08:00 scheduled <missing>
weekly-archive-sweep cron 0 3 * * 0 2026-04-26T03:00:18+08:00 - disabled <missing><missing> flags jobs that have no artifact under the output dir yet — useful for catching silent failures.
html — single-file offline report
hermes-cron-doctor html --output report.html # write file
hermes-cron-doctor html > report.html # or pipe stdout
hermes-cron-doctor html --recent 10 --preview-chars 800 --output out/report.htmlThe output is one self-contained HTML file with inline CSS, no external assets, dark/light auto theme. Each job card is colour-coded by schedule kind:
■ cron blue left bar
■ interval purple left bar
■ once orange left bar
■ disabled grey, dimmedFor each job it shows the schedule expression, last/next run, last status badge, the latest artifact filename + the first ~500 chars as a preview, and a fold of older artifacts. Jobs in last_status: error get a red-tinted error block with the captured last_error message.
Flags
| Flag | Default | Purpose |
|---|---|---|
| --jobs <path> | ~/.hermes/cron/jobs.json | Override the jobs definition file |
| --output-dir <path> | ~/.hermes/cron/output | Override the artifact directory |
| --output <path> (html) | stdout | Write HTML to a file instead of stdout |
| --preview-chars <n> (html) | 500 | How many chars of the latest artifact to inline |
| --recent <n> (html) | 5 | How many recent artifacts to list per job |
| --json (status) | off | Emit JSON instead of the human table |
Expected jobs.json schema
hermes-cron-doctor matches the actual Hermes layout: a top-level object with a jobs array, not a {name: job} dictionary.
{
"jobs": [
{
"id": "abc123",
"name": "daily-morning-briefing",
"enabled": true,
"state": "scheduled",
"schedule": { "kind": "cron", "expr": "30 7 * * *" },
"next_run_at": "2026-05-11T07:30:00+08:00",
"last_run_at": "2026-05-10T07:30:12+08:00",
"last_status": "ok"
}
]
}Supported schedule.kind values: cron (with expr), interval (with minutes), once (with at). The artifact directory is keyed by job id first, falling back to name when id is absent: <output-dir>/<key>/<timestamp>.md.
Development
npm install
npm test # 15 cases under node:test
node bin/cli.js status --jobs fixtures/jobs.mixed.json --output-dir fixtures/output
node bin/cli.js html --jobs fixtures/jobs.mixed.json --output-dir fixtures/output --output out/report.htmlThe fixture covers all four states: cron enabled, interval with an error last status, once not yet run, and a disabled cron (paused with reason).
Layout
bin/cli.js # commander entrypoint
src/jobs.js # strict { jobs: [...] } loader, three error codes
src/artifacts.js # latest + last-N artifact discovery, preview reader
src/status.js # buildStatusReport() + formatStatusReport() table renderer
src/html.js # buildHtmlReport() + renderHtml() (EJS)
templates/report.ejs# single-file HTML, inline CSS, dark/light auto
test/*.test.js # node:test, no external test runner
fixtures/ # offline-runnable jobs.mixed.json + sample artifactsReleasing
npm pack --dry-run is the contract: it must list bin/, src/, templates/, README.md, LICENSE, CHANGELOG.md, package.json. Don't run npm publish from inside an autonomous task — publishing is its own gated step.
Changelog
See CHANGELOG.md.
License
MIT © 2026 coldxiangyu
