6.7 KiB
name, description, version, author, tags
| name | description | version | author | tags | |||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
| recurring-briefings | Set up cron jobs for periodic data-gathering and formatted briefings — weather checks, market updates, monitoring digests, or any recurring "fetch → analyze → deliver" workflow. | 1.0.0 | Hermes Agent |
|
Recurring Briefings
Set up automated daily/weekly briefings that fetch external data, analyze it against user-defined constraints, and deliver formatted reports on a schedule.
Core Pattern
Every recurring briefing follows the same shape:
cron job (scheduled) → fetch data (terminal/curl) → analyze → format → deliver (Telegram)
Step 1 — Define the Briefing
Clarify with the user:
| Question | Example |
|---|---|
| What data? | Weather, stock chatter, news headlines |
| What constraints/filters? | "After 6pm on weekdays," "most talked-about stocks" |
| When? | "Every Monday 8 AM," "Weekdays 8:30 AM" |
| Format? | Bullet list, table-free (Telegram), emoji headers |
Step 2 — Create the Cron Job
Use cronjob(action='create') with these settings:
schedule: cron expression or human string like "0 8 * * 1" (Mon 8 AM)
enabled_toolsets: ["web", "terminal"] # for fetching data
Cron Schedule Cheatsheet
| Pattern | Meaning |
|---|---|
30 8 * * 1-5 |
Mon–Fri at 8:30 AM |
0 8 * * 1 |
Monday at 8 AM |
0 9 * * * |
Every day at 9 AM |
0 9 1 * * |
1st of every month at 9 AM |
Prompt Structure in the Cron Job
The prompt should be self-contained (cron jobs have no conversation context). Include:
- The agent's role — "You are a [weather/stock/market] briefing agent"
- Explicit data sources to call — e.g. "curl -s wttr.in/Knoxville+TN+37919 to get the JSON forecast, then parse hourly data"
- User constraints — "Weekdays only after 6pm, weekends anytime"
- Format instructions — "Output as a Telegram-friendly message with emoji headers"
- Anti-hallucination rule — "Actually call the sources. Do not make up data. If a source fails, note it and move on."
Step 3 — Data Sources
Firecrawl setup: see
references/firecrawl-setup.mdfor installation, API key config, and SDK usage patterns for scraping JS-heavy financial sites.
Weather (free, no API key)
# JSON forecast (3 days)
curl -s "wttr.in/LOCATION?format=j1&days=3"
# Formatted terminal output
curl -s "wttr.in/LOCATION?0&days=3"
Parse hourly data to find dry windows matching the user's time constraints (after 6pm weekdays, anytime weekends). Key fields: chanceofrain, precipMM, tempF, windspeedMiles, weatherDesc.
Stock/Financial Buzz
- Firecrawl (preferred) — see
references/firecrawl-setup.mdfor setup. Bypasses JS-rendered paywalls and blocks that kill raw curl on Yahoo Finance, MarketWatch, etc. Free tier: 500 credits/month (~4 sources × 21 weekdays = 84 credits). Firecrawl explicitly blocks Reddit — do not attempt Reddit URLs with it. - Yahoo Finance quote API —
query1.finance.yahoo.com/v8/finance/chart/SYMBOLstill works with a proper User-Agent header. Use for real-time prices on discovered tickers. - Google News RSS —
news.google.com/rss/search?q=stock+market+todayworks reliably via curl. - Reddit (r/wallstreetbets only) — the
.rssendpoint athttps://www.reddit.com/r/wallstreetbets/.rssreturns valid Atom XML (HTTP 200). This is the ONLY working Reddit access path. Full Reddit scraping landscape:references/reddit-scraping.md. All other subs (r/stocks, r/StockMarket, r/pennystocks) return 403 on both.rssand.json. Parse XML titles for ticker mentions ($TICKER or all-caps symbols).
Formatting for Telegram Delivery
Cron jobs auto-deliver to the home channel. Format for Telegram:
📈 **Morning Stock Buzz — [Date]**
**🔥 Most Talked-About Stocks**
- **TICKER** — why it's trending
**📰 Top Headlines**
- Headline 1
- Headline 2
**📊 Market Snapshot**
- Futures direction, sector trends, notable movers
Use: bold, bullet lists, emoji headers. No tables (Telegram rewrites them to bullets anyway).
Pitfalls
- Self-contained prompts — cron jobs have no memory of past conversations. Every detail the agent needs must be in the prompt.
- Rate-limited APIs — GitHub and Reddit may 429 without proper auth. Use
GITHUB_TOKENenv var for GitHub API calls. - CGNAT blocks external access — if a briefing references a self-hosted service (like Immich), the link must use the local LAN IP, not the CGNAT address.
- Reddit is perma-blocked — the JSON API (
old.reddit.com/r/*.json,reddit.com/r/*/hot.json) returns HTML blocks, not JSON. Firecrawl explicitly rejects Reddit (403: "We do not support this site"). The only working fallback:r/wallstreetbets/.rssreturns valid Atom XML. All other subreddit RSS feeds (r/stocks,r/StockMarket,r/pennystocks) return HTTP 403. Use WSB.rssas the sole Reddit source in briefing prompts; skip Reddit entirely if WSB isn't sufficient. - Firecrawl API key setup — must be added directly to
~/.hermes/.env(terminal echo/append), NOT viahermes config set env.FIRECRAWL_API_KEYwhich rejects the dotted key format. Seereferences/firecrawl-setup.md. - Weekend vs weekday — stock/market briefings should run Mon–Fri only (
30 8 * * 1-5). Weather/mowing briefings can include weekends. - No
web_searchtool — useterminalwithcurlinstead. Design the prompt to use curl-based data collection.
Alternative: no_agent=True (Watchdog Pattern)
For deterministic monitoring where the output is pre-defined (drive health checks, service uptime, disk space alerts), use no_agent=True with a script instead of the LLM-driven approach:
cronjob(
action="create",
name="daily-disk-check",
schedule="0 5 * * *",
script="disk-alert.py", # ~/.hermes/scripts/disk-alert.py
no_agent=True # script stdout = delivery message
)
When to use no_agent=True vs default (LLM-driven):
| Aspect | no_agent=True (watchdog) |
no_agent=False (briefing) |
|---|---|---|
| Cost | Zero tokens per run | LLM inference per run |
| Output | Script stdout verbatim | Agent formats the message |
| Use case | Threshold checks, alerts, pings | Summaries, digests, formatted briefings |
| Silent on no-op | Script prints nothing = silent | Agent always produces output |
Key behaviors:
- Non-empty stdin → delivered as the message
- Empty stdout → SILENT (nothing sent to user — perfect for "all clear" checks)
- Non-zero exit / timeout → error alert sent to user (can't fail silently)