Agent-friendly CLI for monitoring SSL/TLS certificate expiry across many hosts. Designed for both humans (colored table) and agents (stable JSON + Prometheus + GitHub Actions output).
A reference implementation of the principles in Hvorfor en god CLI ofte slår MCP for AI-agenter — exit codes that double as severity, multiple structured output formats, stdin support, and explicit help-ai guidance.
certwatch check ai-ops.dk factus.dk # quick table
certwatch check --from-file domains.txt # bulk check
certwatch check ai-ops.dk --json # stable JSON
certwatch check ai-ops.dk --prometheus # Prometheus exposition format
certwatch check ai-ops.dk --github-actions # CI annotations
echo "factus.dk" | certwatch check # stdin
certwatch help-ai # guidance for AI agentsOutput:
╭────────┬───────────────┬────────────┬──────┬────────╮
│ Status │ Host │ Expires │ Days │ Issuer │
├────────┼───────────────┼────────────┼──────┼────────┤
│ ✓ OK │ ai-ops.dk:443 │ 2026-08-08 │ 88 │ WE1 │
│ ✓ OK │ factus.dk:443 │ 2026-08-08 │ 88 │ WE1 │
╰────────┴───────────────┴────────────┴──────┴────────╯
Total 2 · 2 ok · 0 warning · 0 critical · 0 error
brew tap factusconsulting/tap
brew install certwatchThe tap lives at FactusConsulting/homebrew-tap; certwatch is bumped there automatically on every release.
certwatch is published to a self-hosted Chocolatey feed on GitHub Pages (not the
community repository). Add the source once, then install:
choco source add -n=certwatch -s="https://factusconsulting.github.io/certwatch/chocolatey/index.json"
choco install certwatch --source=certwatch -yUpgrade with choco upgrade certwatch --source=certwatch. The package installs a
single self-contained certwatch.exe and shims it onto your PATH.
Single-file AOT binaries from Releases for Linux, macOS and Windows. Unpack and move to ~/bin/ or /usr/local/bin/. No runtime needed (AOT-compiled).
git clone https://github.com/FactusConsulting/certwatch.git
cd certwatch
dotnet publish src/certwatch -c Release -o ./publishRequires .NET 10 SDK.
| Code | Meaning |
|---|---|
0 |
All OK |
1 |
At least one host hit warning threshold (--warn-days, default 30) |
2 |
At least one host hit critical threshold (--crit-days, default 7) or already expired |
74 |
Network/handshake error on at least one host (and no critical) |
78 |
Configuration error (file not found, no input) |
The fact that exit codes communicate severity means you can use certwatch directly in shell-conditional CI/scripts:
certwatch check --from-file domains.txt --quiet
case $? in
0) echo "all good" ;;
1) echo "renew within 30 days" ;;
2) echo "URGENT — expires within 7 days" ; pager.sh ;;
esacFor humans in a terminal.
Stable snake_case JSON with summary counters:
{
"total": 2,
"ok": 2,
"warning": 0,
"critical": 0,
"errors": 0,
"results": [
{
"host": "ai-ops.dk",
"port": 443,
"ok": true,
"severity": "ok",
"subject": "CN=ai-ops.dk",
"issuer": "CN=WE1, O=Google Trust Services, C=US",
"not_after": "2026-08-08T11:18:00Z",
"days_remaining": 88,
"subject_alt_names": ["DNS Name=ai-ops.dk", "DNS Name=www.ai-ops.dk"],
"key_algorithm": "ECDSA",
"key_size_bits": 256
}
]
}Drop into a node_exporter textfile collector:
# HELP certwatch_days_remaining Days until certificate expiry
# TYPE certwatch_days_remaining gauge
certwatch_days_remaining{host="ai-ops.dk",port="443"} 88
certwatch_days_remaining{host="factus.dk",port="443"} 88
Schedule via cron:
*/15 * * * * certwatch check --from-file /etc/certwatch/domains.txt --prometheus > /var/lib/node_exporter/textfile_collector/certwatch.prom.tmp && mv /var/lib/node_exporter/textfile_collector/certwatch.prom{.tmp,}Emit ::warning:: and ::error:: annotations that show inline in PR diffs and workflow logs:
- name: Cert health check
run: certwatch check --from-file domains.txt --github-actionscertwatch check ai-ops.dk factus.dk # positional args
certwatch check --from-file domains.txt # file (# = comments)
cat domains.txt | certwatch check # stdinMIT © Factus Consulting ApS