blogtato

kantordApache-2.0活跃维护

受 Taskwarrior 启发的命令行 RSS/Atom 阅读器,用 Rust 编写,支持通过 Git 同步订阅和阅读状态,适合习惯终端工作流的用户。

A CLI RSS/Atom feed reader inspired by Taskwarrior

v0.1.27发布于 2026-09-13 23:10 UTC232star6fork同步于 2026-11-11 05:58 UTC

怎么上手

由 README 提炼,执行前请确认
cargo install blogtato
硬件要求按 README 说明

README 未说明

下载:源码 zip·GitHub Releases(安装包)本站不镜像安装包

项目说明

由 README 自动翻译提炼

Taskwarrior 风格的 CLI RSS/Atom 阅读器,用 git 同步多设备且无需账号和服务器

受 Taskwarrior 启发的命令行 RSS/Atom 阅读器,用 Rust 编写,支持通过 Git 同步订阅和阅读状态,适合习惯终端工作流的用户。

核心能力

  • 订阅 RSS/Atom 并用简单查询语言按订阅源、已读状态和日期过滤
  • 用 git 仓库同步多设备数据,冲突自动合并无需手动解决
  • 通过 stdin 管道摄入自建脚本或 cron 生成的 RSS/Atom
  • 支持 OPML 导入其他阅读器订阅,导出匹配文章为 JSONL
  • 可配置 jq 表达式在入库前过滤或改写文章字段

容易踩的坑

jq 是可选运行时依赖,只有配置 ingest_filter 时才需要单独安装;stdin: 前缀的摄入源可导出到 OPML 但无法再导入回来;git 同步需先在 git 托管平台创建私有仓库再执行 blog clone user/repo

内容由模型根据 README 生成,可能不完整或不准确,以官方文档为准。看原文 README

最近发版

5 个版本v0.1.27
  1. v0.1.27最新正式版2026-09-13 · 2026-09-13 23:10 UTC

    Fixed

    • (deps) update patch updates (#238)
    • (deps) update rust crate dirs to v7 (#239)
    • stop fetch/push spinner from clobbering ssh passphrase prompt (#217) (#236)

    Other

    • (deps) update rust crate rstest to v0.27.0 (#240)
  2. v0.1.262026-09-05 · 2026-09-05 23:17 UTC
    查看更新说明

    Added

    • compact mode for grouped output (#235)
    • pipe output through pager when it exceeds terminal height (#233)
    • replace add-script with feed ingest from stdin (#231)
    • add no-remote flag to command sync (#218)

    Fixed

    • (deps) update rust crate quick-xml to v0.42.0 (#232)
    • (deps) update rust crate ureq to v3.4.0 (#226)
    • (deps) update rust crate open to v5.4.2 (#207)
    • (deps) update rust crate rss to v2.1.0 (#206)
    • (deps) update rust crate quick-xml to v0.41.0 (#203)
    • (deps) update rust crate open to v5.3.6 (#201)
    • (deps) update rust crate indicatif to v0.18.6 (#202)
    • (deps) update rust crate atom_syndication to v0.12.9 (#205)
    • (deps) update rust crate itertools to v0.15.0 (#198)
    • (deps) update rust crate quick-xml to v0.40.1 (#188)

    Other

    • update some dependencies (#234)
    • small fixes (#230)
    • (deps) update dtolnay/rust-toolchain digest to 6bed076 (#229)
    • fix minimum dep age logic (#228)
    • (deps) update github actions (#224)
    • improve dependency management (#221)
    • (deps) update actions/checkout digest to 3d3c42e (#213)
    • fix failing lockfile (#215)
    • (deps) update dtolnay/rust-toolchain digest to 4be7066 (#204)
    • update Feedly link
    • (deps) update actions/checkout action to v7 (#199)
    • (deps) update release-plz/action digest to e879257 (#196)
    • (deps) update actions/checkout digest to df4cb1c (#194)
  3. v0.1.252026-05-13 · 2026-05-13 06:57 UTC
    查看更新说明

    Fixed

    • (deps) update rust crate quick-xml to v0.40.0
    • (deps) update rust crate rss to v2.0.13
    • (deps) update rust crate open to v5.3.5
    • (deps) update rust crate atom_syndication to v0.12.8
    • (deps) update rust crate quick-xml to v0.39.4
    • (deps) update rust crate chumsky to v0.13.0
    • (deps) update rust crate quick-xml to v0.39.3

    Other

    • merge
    • (deps) update release-plz/action digest to 064f4d1
    • (deps) update rust crate assert_cmd to v2.2.2
  4. v0.1.242026-05-03 · 2026-05-03 08:31 UTC
    查看更新说明

    Added

    • serialize query back to token syntax via Display impl
    • summary footer with query echo (#172)

    Fixed

    • (deps) update rust crate open to v5.3.4
    • use sort_by instead of sort_by_key for date ordering
    • (deps) update rust crate clap to v4.6.1 (#164)
    • (deps) update rust crate rayon to v1.12.0 (#162)

    Other

    • (deps) update rust crate assert_cmd to v2.2.1
    • (deps) update rust crate libc to v0.2.186
    • make clippy happy
    • fix failing ci and prek-related issues
    • fix clippy error (#170)
    • (deps) update rust crate libc to v0.2.185 (#161)
  5. v0.1.232026-04-06 · 2026-04-06 09:39 UTC
    查看更新说明

    Added

    • add sync only selected feeds

    Other

    • add Custom store location description with RSS_STORE env (#157)

用户点评

登录用户的真实使用记录,可按版本和硬件筛选
共 0 条

    还没有人点评。第一个说出真实体验的人最有价值。

    README 原文

    未翻译

    blogtato

    A CLI RSS/Atom feed reader inspired by Taskwarrior.

    demo

    Features

    • Subscribe to RSS and Atom feeds
    • Ingest RSS or Atom generated by your own scripts, cron jobs, or CI
    • Simple query language for filtering by feed, read status, and date, with grouping and export
    • Git-based sync across machines with conflict-free merge (why git?)
    • No accounts, no servers, no continuous network dependency
    • Mark content as read
    • Designed to be distraction free, minimalistic and work out of the box

    Install

    cargo install blogtato
    

    Git sync

    git based synchronization is entire optional. blogtato can work entirely offline on a single device.

    To set up git synchronization, create a private repo on your git host, then:

    # On your first machine
    blog clone user/repo
    
    # From now on, sync fetches feeds and pushes/pulls from the remote
    # with no remote repository, `blog sync` just pulls the latest posts from
    # all feeds
    blog sync
    

    On your device(s), run the same blog clone to pull down your feeds and posts.

    Don't worry about setting git sync up if you are just trying blogtato out: you can run blog clone user/repo later and your existing feeds will be merged with the remote automatically.

    Quick start

    Once you set up your git-based sync, or if you decided to skip it, subscribe to your favorite feeds using blog feed add:

    blog feed add https://michael.stapelberg.ch
    blog feed add https://www.justinmklam.com
    

    For pages that do not publish RSS or Atom, you can generate a feed yourself with a small local scraper and pipe it into blogtato:

    python /path/to/rss-scrapers/hn-updates.py | blog feed ingest hn-updates
    

    blog feed ingest <name> reads RSS or Atom XML from stdin and stores the resulting posts under a feed named stdin:<name>. Re-running it (from a cron job, a systemd timer, a GitHub Action, or manually) updates that same feed in place — nothing is executed automatically during blog sync, so this works entirely on your terms and with any tool you like.

    {
      "id": "fa61c6ee624",
      "url": "stdin:hn-updates",
      "title": "HN updates",
      "site_url": "https://hnrss.org/",
      "description": "Generated by local script",
      "is_fetched": true
    }
    

    Because ingestion is just "run something, pipe RSS/Atom into blogtato", it composes with anything:

    • A cron job or systemd timer that scrapes a page and ingests it periodically
    • A GitHub Action in your blogtato repo, so blog sync on any machine already has the latest scraped data
    • rsspls or any other scraper that emits RSS/Atom, piped straight into blog feed ingest
    • Different scrapers running on different machines, all ingesting into the same synced store

    You can import your subscriptions from other RSS readers (Feedly, Inoreader, NetNewsWire, FreshRSS, Feeder, Tiny Tiny RSS, Outlook, and others) using an OPML file:

    blog feed import feeds.opml
    

    stdin:-prefixed feeds from blog feed ingest are exported to OPML for reference, but cannot be imported back — an OPML entry has no way to carry the scraper command, so blog feed import rejects them with an error. Ingest those feeds directly instead.

    Fetch and list latest posts:

    blog sync
    blog
    

    Read whatever you found interesting by referring to its shorthand

    blog df read
    

    You can subscribe to blogtato releases to know when new features or fixes are available:

    blog feed add https://github.com/kantord/blogtato/releases.atom
    

    Usage examples

    # Subscribe to a feed
    blog feed add https://news.ycombinator.com/rss
    
    # Fetch new posts and sync with git remote
    blog sync
    
    # Sync only selected feeds by @shorthand from `blog feed ls`
    blog sync --feed @df --feed @dg
    
    # Show posts (defaults to unread posts from the last 3 months, grouped by week)
    blog
    
    # Group by date, week, or feed
    blog /d
    blog /w
    blog /f
    
    # Combine groupings
    blog /d /f
    
    # Filter by feed shorthand
    blog @hn
    
    # Filter by read status
    blog .unread
    blog .read
    blog .all
    
    # Filter by date
    blog 1w..
    blog 3m..1m
    blog /d 2w..1w
    
    # Combine filters - list unread posts form HackerNews grouped by date
    blog @hn .unread /d
    
    # Open a post in the default browser
    blog abc open
    
    # Print a post URL (useful with CLI browsers)
    blog abc read
    w3m $(blog abc read)
    
    # Mark a post as unread
    blog abc unread
    
    # Export matching posts as JSONL
    blog .all export
    blog @myblog export
    blog 1w.. export
    
    # List subscriptions
    blog feed ls
    
    # Remove a feed
    blog feed rm https://news.ycombinator.com/rss
    blog feed rm @hn
    

    Design philosophy

    I built blogtato around the idea of subscription detox and simplicity. I just wanted to use a simple and RSS reader that is not distracting, but can be synced between different devices seamlessly without having to set up another user account and paying another monthly subscription fee.

    blogtato uses a simple database that stores data in JSONL files and syncs them using git. From a performance standpoint, this is admittedly sub-optimal, and an quite esoteric design. At the same time, if you are comfortable with CLI tools you likely have access to a remote git host such as GitHub, GitLab or a Forgejo instance: and that's all blogtato needs to be able to keep data up to date on all of your devices. From a user perspective, this just works with effectively zero configuration.

    blogtato's database uses a conflict-free design: even if you have diverging changes between different devices, you will never have to manually resolve conflicts. You can forget about git being there.

    Network operations are always initiated by the user. There is no need for a continuously running server. And all operations that don't strictly need network access work offline.

    It is my goal to keep the feature-set and the complexity of this project down, so that it can be maintained with minimal effort and can be considered to be "done".

    Optional configuration

    blogtato is designed to work out of the box without any configuration. Most users should never need to change any settings. Nevertheless, a few things can be configured, when the nature of the synchronized database calls for measures to protect data consistency, or in other cases where taking advantage of the synced database for storing the configuration would be convenient.

    Configuration is stored in the same git-based database as your feeds and posts, so settings automatically sync across all your devices.

    Custom store location

    By default, blogtato stores data in the platform-specific data directory:

    • macOS: ~/Library/Application Support/blogtato/stores/default
    • Linux: $XDG_DATA_HOME/blogtato/stores/default (typically ~/.local/share/blogtato/stores/default)

    You can override this location by setting RSS_STORE env:

    export RSS_STORE=/path/to/another/store
    

    Custom default query

    By default, blog with no arguments shows unread posts from the last 3 months grouped by week. You can change this:

    # Show posts from the past 3 months grouped by date instead
    blog config set default_query '.all /d 3m..'
    
    # Show unread posts from the last week
    blog config set default_query '.unread 1w..'
    
    # Reset to built-in default
    blog config unset default_query
    

    Ingest filter

    You can configure a jq expression that transforms posts before they are stored, whether they arrive via blog sync or blog feed ingest. This lets you filter out unwanted content or rewrite fields. The expression receives an array of post objects and must return an array.

    blogtato calls jq as an external process. It is not bundled with blogtato

    • it is an optional runtime dependency, only needed if you configure an ingest_filter. When no filter is set, all posts are stored as-is.

    jq was chosen over arbitrary shell scripting because feed data is structured JSON, which is difficult to manipulate correctly with most other shell tools, so users would otherwise just create shell script files that are a boilerplate wrapper over jq. jq is the standard tool for this purpose that most CLI users already have installed and are familiar with.

    Also, calling an arbitrary binary/script file would be a runtime dependency that does not automatically sync to other machines, so one could easily create runtime errors or load inconsistent data in the database. Storing a jq expression in the synced database means it automatically carries over to all your devices.

    # Filter out sponsored posts
    blog config set ingest_filter '[.[] | select(.title | startswith("[Sponsored]") or contains("Partner Content") | not)]'
    
    # Strip tracking parameters from links
    blog config set ingest_filter '[.[] | .link |= sub("\\?utm.*"; "")]'
    
    # Combine both: filter sponsored posts and strip tracking parameters
    blog config set ingest_filter '[.[] | select(.title | startswith("[Sponsored]") or contains("Partner Content") | not) | .link |= sub("\\?utm.*"; "")]'
    
    # Remove the filter
    blog config unset ingest_filter
    

    Each post object has these fields: title, date, link, raw_id, feed.

    Naming

    The naming is meant to symbolize simplicity and pragmatic silliness: I just mashed the word "blog" together with the first word I could think of: potato.

    Comparison with alternatives

    blogtato is relatively new and there are several good, and more mature alternatives. This section attempts to summarize how they differ from blogtato and why some users might still prefer to use blogtato instead.

    Newsboat

    Newsboat is a very mature TUI RSS client with a wide range of features that blogtato does not have. Just like blogtato, it supports local-only workflows.

    It can also act as a client to remote servers, so if you are ok with having to self-host a server or signing up to a hosted server, then blogtato's git sync feature is not a relevant differentiator for you.

    Another reason why blogtato might be relevant to you is that it has a Taskwarrior-like interface that is even more minimalistic than Newsboat.

    Newsraft

    Newsraft is a more minimalistic alternative to Newsboat. The Newsboat breakdown mostly applies.

    FreshRSS, Miniflux & Tiny Tiny RSS

    These are mature, self-hostable web-based RSS readers/aggregators. What blogtato offers in comparison is a minimalistic CLI interface and effectively zero-setup sync between different machines using git, without the need for an additional, continuously running server.

    Feedly, Inoreader

    These are full-featured web-based services. If you are a heavy RSS-user and like the user interfaces and features they offer (such as GUI apps for iOS and Android, content recommendations) you will likely prefer one of these options.

    You could find blogtato interesting if you'd prefer a more minimalistic, distraction-free CLI interface and do not need their advanced features.