# RIFM **Repository Path**: forantar/rifm ## Basic Information - **Project Name**: RIFM - **Description**: "Read It For Me" - per-topic news digests with local history and a browse UI - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-09 - **Last Updated**: 2026-09-14 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # RIFM — Read It For Me Per-topic news digests with local history and a browse UI. ## Usage | Command | What it does | |---|---| | `/topics` | Numbered list of topics; reply with a number to read it | | `/read ` | Digest of **new** items since the last read | | `/read full` | Full digest, ignoring read state (10-day window) | | `/add` | Interview (topic → short name → sources) and register a new topic | | `/remove ` | Remove a topic (asks which one if no argument) | | `/browse` | Open the digest browser page in your default browser | | `/stop-server` | Stop the browse server (otherwise it exits after 30 min idle) | A digest ends with a stats block (sources, duration, token usage measured from the session transcript). Fetching and filtering are pure script (`scripts/fetch.py`, no LLM); the LLM only selects and composes, so incremental reads cost little. When nothing new appeared, you get a one-liner followed by the newest stored digest. Every run that produced items is stored dated, so history accumulates in `.rifm/..md`. ## Browse page `/browse` builds a single self-contained page and serves it from a tiny localhost server (`127.0.0.1:8642`, auto-started, exits after 30 min idle): - home view lists every topic; a topic lists its digests by date, newest first; a digest shows its text with clickable URLs - Home and Back buttons in the top-left corner of every view - prev/next arrows above a digest page step through that topic's digests by date (each disabled at the end it can't go past) - the "Read now" card on a topic runs `/read ` headlessly (`claude -p`), shows live progress (sources N/M, then composing), and the tab refreshes itself with the fresh digest when it lands — no permission dialogs The headless read uses `--permission-mode bypassPermissions` (it cannot answer tool prompts) and only ever runs `/read` on a key that exists in `topics.json`; its raw event log lands in `.rifm/last-read.log`. ## Layout ``` topics.json single registry: topics + their news providers scripts/topics.sh topic listing (with digest counts); add/remove registry subcommands (atomic jq edits) scripts/fetch.py mechanical half of /read: fetch, filter, state scripts/verify-feeds.py feed health checks for /add (rss/html/fail) scripts/browse.sh builds the browse page; --build = rebuild only scripts/browse-server.py localhost server: page, /read trigger, /status .claude/skills/read/ digest skill (fetch, filter, format, stats, state) .claude/skills/topics/ topic-list skill (wraps scripts/topics.sh) .claude/skills/browse/ browse-page skill (wraps scripts/browse.sh) .rifm/ gitignored, machine-local: .json per-topic read state ..md one stored digest per run that had items browse.html the generated page last-read.log raw event stream of the last headless read ``` Browse needs `jq`, `python3`, and `curl`; without python3 it falls back to opening the static file with "Read now" disabled. ## Topics and sources Each topic in `topics.json` hardcodes its own provider list: ```json "ai-models": { "title": "AI models", "focus": ["...what counts as relevant..."], "exclude": ["...what to drop..."], "sources": [{ "url": "...", "type": "rss", "note": "..." }] } ``` - `type: "rss"` — feed URLs (preferred): dated entries, incremental reads, gists from the feed's own description - `type: "html"` — page fallback: the page is fetched and summarized; newness is tracked by a seen-URL list