blogwatcher-db
Query the blogwatcher-cli SQLite database for RSS scan results. Use pre-verified column names and query templates to avoid errors.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Query the blogwatcher-cli SQLite database for RSS scan results. Use pre-verified column names and query templates to avoid errors.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
Active crawl workflow — reads hot-topics.yaml, selects high-priority un-crawled topics, deep-dives via web search, creates wiki concept pages, and updates tracking
Analyze Chinese AI media, newsletter, and crawl items for durable trends, source differences, and wiki actions
Triage crawled Chinese AI articles from inbox/ into structured Japanese wiki pages following SCHEMA.md conventions
Upgrade bio-only blogger entity pages to comprehensive thought analysis format
Enrich skeleton X/Twitter account entity pages to full quality (8-15KB), matching antirez-com.md / simon-willison.md depth.
Check and process emails on exe.dev VM using Maildir filesystem. Use this when user asks to check emails or when newsletters arrive.
| name | blogwatcher-db |
| description | Query the blogwatcher-cli SQLite database for RSS scan results. Use pre-verified column names and query templates to avoid errors. |
| category | research |
| verified | "2026-04-13T00:00:00.000Z" |
Pre-verified query templates for the blogwatcher-cli SQLite database. DO NOT guess column names — use only the schema documented below.
/home/exedev/.blogwatcher-cli/blogwatcher-cli.dbblogs| Column | Type | Constraints | Description |
|---|---|---|---|
id | INTEGER | PRIMARY KEY | Blog ID |
name | TEXT | NOT NULL | Blog name (e.g., "simonwillison.net", "r/LocalLLaMA") |
url | TEXT | NOT NULL, UNIQUE | Blog homepage URL |
feed_url | TEXT | nullable | RSS/Atom feed URL |
scrape_selector | TEXT | nullable | CSS selector for HTML scraping fallback |
last_scanned | TIMESTAMP | nullable | Last scan timestamp (ISO 8601) |
articles| Column | Type | Constraints | Description |
|---|---|---|---|
id | INTEGER | PRIMARY KEY | Article ID |
blog_id | INTEGER | NOT NULL, FK→blogs.id | Source blog ID |
title | TEXT | NOT NULL | Article title |
url | TEXT | NOT NULL, UNIQUE | Article URL |
published_date | TIMESTAMP | nullable | When article was published (ISO 8601 from RSS) |
discovered_date | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | When blogwatcher discovered it |
is_read | BOOLEAN | DEFAULT FALSE (0/1) | Read/unread status |
categories | TEXT | nullable | JSON array of tags, e.g. ["llms","machine-learning"] |
schema_migrations| Column | Type | Description |
|---|---|---|
version | uint64 | Migration version |
dirty | bool | Migration state |
These are commonly guessed but do not exist:
published_at → use published_date or discovered_datesource → use blog_id JOIN blogs.nameauthor → not trackedcontent / body / text → not stored (URLs only)tags → use categories (JSON array)created_at → use discovered_dateupdated_at → not trackedSELECT b.name, a.title, a.url, a.published_date, a.discovered_date, a.categories
FROM articles a JOIN blogs b ON a.blog_id = b.id
WHERE DATE(a.discovered_date) = 'YYYY-MM-DD'
ORDER BY b.name, a.discovered_date DESC;
SELECT b.name, a.title, a.url, a.discovered_date
FROM articles a JOIN blogs b ON a.blog_id = b.id
WHERE a.is_read = 0
ORDER BY a.discovered_date DESC;
SELECT a.title, a.url, a.published_date, a.discovered_date, a.is_read, a.categories
FROM articles a JOIN blogs b ON a.blog_id = b.id
WHERE b.name = 'simonwillison.net'
ORDER BY a.discovered_date DESC;
SELECT a.title, a.url, a.published_date, a.discovered_date, a.categories
FROM articles a JOIN blogs b ON a.blog_id = b.id
WHERE b.name = 'r/LocalLLaMA'
AND DATE(a.discovered_date) BETWEEN 'YYYY-MM-DD' AND 'YYYY-MM-DD'
ORDER BY a.discovered_date DESC;
SELECT b.name, COUNT(*) as cnt
FROM articles a JOIN blogs b ON a.blog_id = b.id
GROUP BY b.name
ORDER BY cnt DESC;
SELECT b.name, COUNT(*) as cnt
FROM articles a JOIN blogs b ON a.blog_id = b.id
WHERE DATE(a.discovered_date) = 'YYYY-MM-DD'
GROUP BY b.name
ORDER BY cnt DESC;
SELECT b.name, a.title, a.url, a.discovered_date, a.categories
FROM articles a JOIN blogs b ON a.blog_id = b.id
WHERE a.categories LIKE '%"AI%"%'
OR a.categories LIKE '%"llm%"%'
OR a.categories LIKE '%"machine-learning%"%'
ORDER BY a.discovered_date DESC;
SELECT b.name, a.title, a.url, a.published_date, a.discovered_date, a.categories
FROM articles a JOIN blogs b ON a.blog_id = b.id
WHERE a.discovered_date > datetime('now', '-N days')
ORDER BY a.discovered_date DESC;
import sqlite3
import json
import os
DB_PATH = "/home/exedev/.blogwatcher-cli/blogwatcher-cli.db"
def query_daily_scan(date_str):
"""Get all articles discovered on a specific date, grouped by blog."""
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
rows = conn.execute("""
SELECT b.name, a.title, a.url, a.published_date, a.discovered_date, a.categories
FROM articles a JOIN blogs b ON a.blog_id = b.id
WHERE DATE(a.discovered_date) = ?
ORDER BY b.name, a.discovered_date DESC;
""", (date_str,)).fetchall()
# Group by blog
blogs = {}
for row in rows:
if row['name'] not in blogs:
blogs[row['name']] = []
blogs[row['name']].append({
'title': row['title'],
'url': row['url'],
'published_date': row['published_date'],
'discovered_date': row['discovered_date'],
'categories': json.loads(row['categories']) if row['categories'] else []
})
conn.close()
return blogs
def generate_daily_report(date_str, blogs):
"""Generate markdown report for inbox/rss-scans/."""
total = sum(len(arts) for arts in blogs.values())
md = f"""# Daily RSS Scan Report — {date_str}
> Source: blogwatcher-cli RSS scan
> Total articles: {total}
"""
# Sort: individual blogs first, then Reddit subs
order = sorted(blogs.keys(), key=lambda x: (x.startswith('r/'), x.lower()))
for blog_name in order:
md += f"## {blog_name}\n\n"
for art in blogs[blog_name]:
md += f"- [{art['title']}]({art['url']})\n"
md += "\n"
return md
~/ai-topics-cn/inbox/rss-scans/daily-scan-YYYY-MM-DD.mdcd ~/ai-topics-cn && git add inbox/rss-scans/ && git commit -m "wiki: daily RSS scan YYYY-MM-DD" && git pushpublished_at does not exist → use published_date or discovered_datesource does not exist → JOIN blogs.name via blog_idcategories is JSON, not comma-separated → use LIKE '%"tag"%' or parse with json.loads()is_read is 0/1 → use WHERE is_read = 0 not WHERE is_read = FALSEDATE() function → DATE(a.discovered_date) = 'YYYY-MM-DD' for date filteringweb_extract if neededpublished_date can be NULL → articles without RSS pub date have this set to NULLlast_scanned on blogs → ISO 8601 format, use to check freshness