blogwatcher
Monitor blogs and RSS/Atom feeds for updates using the blogwatcher-cli tool. Add blogs, scan for new articles, track read status, and filter by category.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Monitor blogs and RSS/Atom feeds for updates using the blogwatcher-cli tool. Add blogs, scan for new articles, track read status, and filter by category.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
A test skill for the autoload integration test (alpha)
A test skill for the autoload integration test (beta)
A test skill for the autoload integration test (gamma)
Review, position, and polish Agent View Layer (AVL) / agent-first web projects: .agent pages, agent.txt manifests, AI-native rendering, MCP/API positioning, and marketing/category narrative.
Reconstruct, critique, and rewrite ArgentOS.ai positioning and website copy using Hermes's external-auditor role, the user's Medium/Substack scar-tissue articles, and stored Obsidian source-of-truth documents. Use when working on ArgentOS messaging, homepage copy, Core vs Business positioning, ecosystem framing, identity/philosophy pages, or claims/trust language.
Full pull request lifecycle — create branches, commit changes, open PRs, monitor CI status, auto-fix failures, and merge. Works with gh CLI or falls back to git + GitHub REST API via curl.
| name | blogwatcher |
| description | Monitor blogs and RSS/Atom feeds for updates using the blogwatcher-cli tool. Add blogs, scan for new articles, track read status, and filter by category. |
| version | 2.0.0 |
| author | JulienTant (fork of Hyaxia/blogwatcher) |
| license | MIT |
| metadata | {"hermes":{"tags":["RSS","Blogs","Feed-Reader","Monitoring"],"homepage":"https://github.com/JulienTant/blogwatcher-cli"}} |
| prerequisites | {"commands":["blogwatcher-cli"]} |
Track blog and RSS/Atom feed updates with the blogwatcher-cli tool. Supports automatic feed discovery, HTML scraping fallback, OPML import, and read/unread article management.
Pick one method:
go install github.com/JulienTant/blogwatcher-cli/cmd/blogwatcher-cli@latestdocker run --rm -v blogwatcher-cli:/data ghcr.io/julientant/blogwatcher-clicurl -sL https://github.com/JulienTant/blogwatcher-cli/releases/latest/download/blogwatcher-cli_linux_amd64.tar.gz | tar xz -C /usr/local/bin blogwatcher-clicurl -sL https://github.com/JulienTant/blogwatcher-cli/releases/latest/download/blogwatcher-cli_linux_arm64.tar.gz | tar xz -C /usr/local/bin blogwatcher-clicurl -sL https://github.com/JulienTant/blogwatcher-cli/releases/latest/download/blogwatcher-cli_darwin_arm64.tar.gz | tar xz -C /usr/local/bin blogwatcher-clicurl -sL https://github.com/JulienTant/blogwatcher-cli/releases/latest/download/blogwatcher-cli_darwin_amd64.tar.gz | tar xz -C /usr/local/bin blogwatcher-cliAll releases: https://github.com/JulienTant/blogwatcher-cli/releases
By default the database lives at ~/.blogwatcher-cli/blogwatcher-cli.db. In Docker this is lost on container restart. Use BLOGWATCHER_DB or a volume mount to persist it:
# Named volume (simplest)
docker run --rm -v blogwatcher-cli:/data -e BLOGWATCHER_DB=/data/blogwatcher-cli.db ghcr.io/julientant/blogwatcher-cli scan
# Host bind mount
docker run --rm -v /path/on/host:/data -e BLOGWATCHER_DB=/data/blogwatcher-cli.db ghcr.io/julientant/blogwatcher-cli scan
If upgrading from Hyaxia/blogwatcher, move your database:
mv ~/.blogwatcher/blogwatcher.db ~/.blogwatcher-cli/blogwatcher-cli.db
The binary name changed from blogwatcher to blogwatcher-cli.
blogwatcher-cli add "My Blog" https://example.comblogwatcher-cli add "My Blog" https://example.com --feed-url https://example.com/feed.xmlblogwatcher-cli add "My Blog" https://example.com --scrape-selector "article h2 a"blogwatcher-cli blogsblogwatcher-cli remove "My Blog" --yesblogwatcher-cli import subscriptions.opmlblogwatcher-cli scanblogwatcher-cli scan "My Blog"blogwatcher-cli articlesblogwatcher-cli articles --allblogwatcher-cli articles --blog "My Blog"blogwatcher-cli articles --category "Engineering"blogwatcher-cli read 1blogwatcher-cli unread 1blogwatcher-cli read-allblogwatcher-cli read-all --blog "My Blog" --yesAll flags can be set via environment variables with the BLOGWATCHER_ prefix:
| Variable | Description |
|---|---|
BLOGWATCHER_DB | Path to SQLite database file |
BLOGWATCHER_WORKERS | Number of concurrent scan workers (default: 8) |
BLOGWATCHER_SILENT | Only output "scan done" when scanning |
BLOGWATCHER_YES | Skip confirmation prompts |
BLOGWATCHER_CATEGORY | Default filter for articles by category |
$ blogwatcher-cli blogs
Tracked blogs (1):
xkcd
URL: https://xkcd.com
Feed: https://xkcd.com/atom.xml
Last scanned: 2026-04-03 10:30
$ blogwatcher-cli scan
Scanning 1 blog(s)...
xkcd
Source: RSS | Found: 4 | New: 4
Found 4 new article(s) total!
$ blogwatcher-cli articles
Unread articles (2):
[1] [new] Barrel - Part 13
Blog: xkcd
URL: https://xkcd.com/3095/
Published: 2026-04-02
Categories: Comics, Science
[2] [new] Volcano Fact
Blog: xkcd
URL: https://xkcd.com/3094/
Published: 2026-04-01
Categories: Comics
Use this when blogwatcher-cli is not installed, when the site blocks browser access with Cloudflare, or when you need a self-contained scheduled digest. Medium author pages may show a bot challenge in the browser, but their RSS feed is often still accessible at:
https://medium.com/feed/@USERNAME
Pattern:
urllib.request and a normal User-Agent.xml.etree.ElementTree.content:encoded (http://purl.org/rss/1.0/modules/content/).~/.hermes/state/.~/.hermes/scripts/ and schedule it with cronjob; cron script paths must be relative to ~/.hermes/scripts/, not absolute paths.Minimal script shape:
#!/usr/bin/env python3
import html, json, re, urllib.request, xml.etree.ElementTree as ET
from pathlib import Path
FEED_URL = "https://medium.com/feed/@USERNAME"
STATE_PATH = Path.home() / ".hermes" / "state" / "medium_seen.json"
def clean(raw):
raw = re.sub(r"</(p|h\\d|li|blockquote)>", "\n", raw or "", flags=re.I)
raw = re.sub(r"<br\\s*/?>", "\n", raw, flags=re.I)
txt = re.sub(r"<[^>]+>", " ", raw)
txt = html.unescape(txt)
txt = re.sub(r"\n\s*\n+", "\n", txt)
txt = re.sub(r"[ \\t]+", " ", txt)
return txt.strip()
req = urllib.request.Request(FEED_URL, headers={"User-Agent": "Mozilla/5.0"})
root = ET.fromstring(urllib.request.urlopen(req, timeout=30).read())
ns = {"content": "http://purl.org/rss/1.0/modules/content/"}
items = []
for item in root.find("channel").findall("item"):
encoded = item.find("content:encoded", ns)
content = clean(encoded.text if encoded is not None else "")
guid = item.findtext("guid") or item.findtext("link") or item.findtext("title")
items.append({
"guid": guid,
"title": item.findtext("title") or "Untitled",
"link": item.findtext("link") or "",
"published": item.findtext("pubDate") or "",
"content": content,
"word_count": len(content.split()),
})
STATE_PATH.parent.mkdir(parents=True, exist_ok=True)
seen = set()
if STATE_PATH.exists():
seen = set(json.loads(STATE_PATH.read_text()).get("seen", []))
new_items = [i for i in items if i["guid"] not in seen]
STATE_PATH.write_text(json.dumps({"feed_url": FEED_URL, "seen": sorted(seen | {i["guid"] for i in items})}, indent=2))
print(f"Total articles in feed: {len(items)}")
print(f"New articles since last run: {len(new_items)}")
for article in new_items:
print("\n" + "=" * 80)
print(article["title"])
print(article["link"])
print(article["published"])
print(article["content"])
Schedule it:
cronjob(action="create",
name="Follow Medium feed",
schedule="every 24h",
script="medium_seen.py", # relative to ~/.hermes/scripts/
prompt="The pre-run script prints any new articles. If none, say so exactly. If new articles exist, read them fully and produce a digest with summary, key claims, implications, critique, and follow-up questions.",
deliver="origin",
enabled_toolsets=["web"])
--feed-url is provided.--scrape-selector is configured.~/.blogwatcher-cli/blogwatcher-cli.db by default (override with --db or BLOGWATCHER_DB).blogwatcher-cli <command> --help to discover all flags and options.