en un clic
content-digests
Build and deliver periodic content digests (news, stock, entertainment) to multiple platforms: save to vault Raw/ and print full markdown to stdout for cron-based delivery.
Menu
Build and deliver periodic content digests (news, stock, entertainment) to multiple platforms: save to vault Raw/ and print full markdown to stdout for cron-based delivery.
| name | content-digests |
| description | Build and deliver periodic content digests (news, stock, entertainment) to multiple platforms: save to vault Raw/ and print full markdown to stdout for cron-based delivery. |
| version | 1.0.0 |
| author | Hermes Agent |
| license | MIT |
| metadata | {"hermes":{"tags":["content","digests","news","aggregation","multi-platform"]}} |
This umbrella skill governs the construction and delivery of periodic content digests — news, stock updates, entertainment listings, and similar recurring data aggregations — to multiple platforms (Discord, Telegram, Slack) while archiving to an Obsidian vault.
Dual output — Every digest script must:
Raw/<Category>/ directory for permanent archivalPlatform-agnostic — The same script drives all delivery channels. Cron configuration decides which platforms receive the stdout capture. No per-platform script branching.
Chunking for size-limited platforms — Telegram (4096 char limit) and Slack (~4000 chars) require splitting large digests into sequential chunks with part numbering. Discord handles larger messages (~8KB) but chunking still improves UX.
Vault-first organization — All content lives in Raw/<Category>/<Date> <Digest Name>.md inside the Obsidian vault. The filename convention is YYYY-MM-DD <Digest Name>.md.
Load content-digests when:
from pathlib import Path
import os, datetime
VAULT = Path.home() / "obsidian-vault" / "FACorreia" # adjust vault name
RAW_DIR = VAULT / "Raw" / "<Category>" # e.g. Raw/Dev/Swift, Raw/Entertainment
RAW_DIR.mkdir(parents=True, exist_ok=True)
now = datetime.datetime.now()
outfile = RAW_DIR / f"{now.strftime('%Y-%m-%d')} <Digest Name>.md"
md string)Consistent header:
md = f"""# <Digest Name> — {now.strftime('%Y-%m-%d %H:%M')}
> Summary line with counts per source
---
"""
md += "\n".join(entries) # your collected items
md += f"\n\n✅ Saved to `{outfile}`"
with open(outfile, 'w') as f:
f.write(md)
print(f"✅ <Digest> saved ({len(entries)} items) → {outfile}")
Critical: This must be the LAST print statement, outside all loops, with no extra formatting before or after (except optional separator).
# Separator helps platform delivery bots distinguish payload
print("\\n" + "="*60)
print(md) # FULL markdown content
Pitfall: Double-printing (inside/outside loops) causes duplicate messages. Ensure only one print(md) call after saving.
If the script is invoked per-platform with size limits, wrap the final delivery in a chunker:
def chunk_send(content, limit=3900):
"""Yield chunks ending at newline boundaries."""
lines = content.split('\n')
chunk = []
size = 0
for line in lines:
line_nl = line + '\n'
if size + len(line_nl) > limit and chunk:
yield ''.join(chunk)
chunk = [line_nl]
size = len(line_nl)
else:
chunk.append(line_nl)
size += len(line_nl)
if chunk:
yield ''.join(chunk)
full_payload = "\\n" + "="*60 + "\\n" + md
for idx, part in enumerate(chunk_send(full_payload), 1):
header = f"🔹 <Digest> (Part {idx}/{})" if total>1 else f"🔹 <Digest>"
print(header + "\\n\\n" + part)
However, prefer letting the cron delivery bot handle chunking by delivering the full stdout to each configured platform. Only implement manual chunking in the script if the bot doesn't support automatic splitting.
| Digest | Vault Category | Channel (Discord) | Frequency | Sources |
|---|---|---|---|---|
| Swift News | Raw/Dev/Swift/ | #swift-news (1499908671847661578) | Daily 09:00 | Hacking with Swift, Swift Forums, HN, GitHub |
| Go News | Raw/Dev/Go/ | #go-news (1499908914500862123) | Daily 09:00 | Go Blog, Go Forum, HN, GitHub |
| Stock News | Raw/Dev/Stock/ | #stock-plan (1499338003334561843) | Daily 09:00 | Yahoo Finance, Motley Fool |
| Entertainment | Raw/Entertainment/ | #entertainment (1499331939469889656) | Daily/Weekly | TVmaze, MyAnimeList, IMDb |
| IMDb Weekly | Raw/Movies/, Raw/TV/Seasonal/, Raw/Anime/ | #entertainment | Weekly (Fri) | IMDb datasets |
# 1. Script runs without error
python3 digest_script.py
# 2. File saved to vault
ls -l /opt/data/obsidian-vault/FACorreia/Raw/Dev/Swift/$(date +%Y-%m-%d)\ *.md
# 3. Stdout contains full markdown (not just summary)
python3 digest_script.py | head -c 500 # should show content, not just "✅ saved"
# 4. Cron job configured with correct model (for LLM-backed digests)
hermes cron list | grep <digest-name>
# Model should be anthropic/claude-sonnet-4.6 (NOT openrouter/anthropic/claude-sonnet-4)
| Pitfall | Symptom | Fix |
|---|---|---|
print(md) inside a loop | Duplicate content in channel | Move print(md) to AFTER the loop, at root scope |
| Forgetting stdout entirely | Cron delivers only the summary line | Add print(md) after file save |
| Printing debug to stdout | Debug text appears in Telegram/Discord | Send diagnostics to sys.stderr or a log file |
| Platform 400s due to oversize | Telegram rejects message (>4096 chars) | Chunk output or reduce digest size |
| Wrong output directory | File saved but cron can't find it | Ensure stdout is captured, not just file I/O |
| Security restrictions on piped commands | Command fails with security warning | Use intermediate file: script.py > /tmp/output && script2.py < /tmp/output instead of `script.py |
hermes cron create "0 9 * * *" \
--name "daily-swift-news" \
--prompt "Aggregate Swift news and deliver to Discord #swift-news" \
--script "swift_news_digest.py" \
--model "anthropic/claude-sonnet-4.6" \
--deliver "discord:1499908671847661578"
Multi-platform variant — Create separate jobs with same script but different --deliver targets (Discord, Telegram, Slack) to ensure all channels receive the digest:
# Discord
hermes cron create "0 9 * * *" --name "swift-news-discord" \
--script "swift_news_digest.py" --model "anthropic/claude-sonnet-4.6" \
--deliver "discord:1499908671847661578"
# Telegram Home
hermes cron create "0 9 * * *" --name "swift-news-telegram" \
--script "swift_news_digest.py" --model "anthropic/claude-sonnet-4.6" \
--deliver "telegram:476978568"
# Slack Home
hermes cron create "0 9 * * *" --name "swift-news-slack" \
--script "swift_news_digest.py" --model "anthropic/claude-sonnet-4.6" \
--deliver "slack:C0B0BDGEJTT"
Alternatively, a single job with --deliver origin broadcasts to all configured platforms if the gateway is set up to mirror.
references/multi-platform-delivery-pattern.md — stdout-driven delivery and chunking strategiesreferences/vault-raw-convention.md — Raw/<Category>/ file organizationscripts/digest-template.py — minimal working example (see scripts/ directory)Scan vault raw/ folder for hyperlinks embedded in markdown notes; POST each unseen URL to LuminaVault server /v1/capture/safari so Hermes ingests + memorizes. Usage: /kb-vacuum [folder=raw/]
Compile all uncompiled raw/ content into the wiki. Writes source summaries, creates/updates concept articles with Obsidian backlinks, and updates the index. Run after /kb-ingest to process new content.
Generate daily Reddit and X/Twitter marketing content from the AI Cohort scoreboard, with automatic fallback to the most recent available data when today's file is missing.
Configuration, maintenance, and troubleshooting of AI Cohort scoreboard scripts including vault path setup and script updates.
Capture external content (X/Twitter articles, web posts) and ingest into Obsidian vault Raw/ with automatic theme detection, summarization, and structured frontmatter. Handles X/fixupx links via multi-strategy extraction (direct fetch → r.jina.ai → nitter fallback).
Weekly founder content research and production engine that transforms trending news into ready-to-use content assets.