A command-line terminal-based feed reader.
Go to file
Jake Bauer 4d72527f1e Fix link to build status badge 2023-11-27 22:04:03 +01:00
.gitignore Set repository up for compilation with crdx/pyinstaller 2023-11-27 18:45:09 +01:00
Jenkinsfile Set repository up for compilation with crdx/pyinstaller 2023-11-27 18:45:09 +01:00
LICENSE Initial checkin 2023-07-26 18:11:46 -04:00
README.md Fix link to build status badge 2023-11-27 22:04:03 +01:00
config.py Initial checkin 2023-07-26 18:11:46 -04:00
database.py Initial checkin 2023-07-26 18:11:46 -04:00
fenen.py Bump version number 2023-11-27 21:12:04 +01:00
fenen.spec Set repository up for compilation with crdx/pyinstaller 2023-11-27 18:45:09 +01:00
html_converter.py Initial checkin 2023-07-26 18:11:46 -04:00
requirements.txt Set repository up for compilation with crdx/pyinstaller 2023-11-27 18:45:09 +01:00
ui_context.py Initial checkin 2023-07-26 18:11:46 -04:00


Fenen Feed Reader

Build Status Static Badge Static Badge

Fenen is a terminal based RSS/Atom feed reader with a command syntax reminiscent of mail(1)/ed(1) with messages/lines replaced by feeds and feed entries.

It's the feed reader that fits my needs :)


The easiest way to get fenen is to just grab one of the pre-packaged binaries for your system, which contain a Python environment and all the dependencies. These are available on the project page.

From Source

If you just want to run fenen without packaging it, ensure you have Python >=3.6 installed.

If you want to package fenen into a single executable file for yourself, ensure you have Python >=3.7 installed and install pyinstaller via pip:

pip install pyinstaller

You can then run the following to turn fenen into a single executable file (which will be found under the ./dist/ folder) for your current combination of operating system and architecture:

pyinstaller --onefile fenen.py

The only external dependency fenen needs is feedparser which is likely already available in your operating system's package repositories.


pkg_add py3-feedparser

Debian Linux & Derivatives (Ubuntu, Linux Mint, etc.)

apt install python3-feedparser

Fedora Linux & Derivatives

dnf install python3-feedparser

My OS/Distro Isn't Listed

Search your OS/Distro's package repositories for the python feedparser package. Some have different packages for different versions of Python so I didn't bother listing them all above.

If they don't have it then your best option is probably just to install it via pip (install it globally or using whichever virtual environment manager you wish):

pip install feedparser

Getting Started

When you launch fenen, you will be greeted with a prompt:

fenen (all)>

This is the command line that you use to interact with fenen. If you've ever used ed(1) or mail(1) before, fenen's syntax is a bit similar to those. The word in parentheses indicates the current context:

  • all -> The list of all feeds
  • feed -> The list of entries in a particular feed
  • unread -> The list of unread entries across all feeds
  • search -> The list of search results (can be either feeds or entries)

To get started, add some feeds to fenen:

fenen (all)> add http://www.paritybit.ca/feed.xml
fenen (all)> add https://www.undeadly.org/cgi?action=rss
fenen (all)> add https://rakudoweekly.blog/

You can use the show command to show all the feeds fenen knows about:

fenen (all)> show
1) http://www.paritybit.ca/feed.xml (0 unread)
2) https://rakudoweekly.blog/ (0 unread)
3) https://www.undeadly.org/cgi?action=rss (0 unread)
fenen (all)>

Although you have just added some feeds to fenen, they are not yet populated with content. To refresh these feeds and load new content, use the refresh command:

fenen (all)> refresh
Refreshed 3/3 feeds in 1 seconds
fenen (all)>

Now when you show again, it should look something like:

fenen (all)> show
1) OpenBSD Journal (9 unread)
2) paritybit.ca (50 unread)
3) Rakudo Weekly News (10 unread)
fenen (all)>

To view a list of posts in a specific feed, use the show command with the number of the feed:

fenen (all)> show 1
OpenBSD Journal - https://www.undeadly.org/cgi?action=rss
1) * 2023-06-21 [CFT] Major pfsync(4) Rewrite on the Horizon
2) * 2023-06-24 Game of Trees 0.90 released
3) * 2023-07-04 [CFT] sec(4) for Route Based IPSec VPNs
4) * 2023-07-06 Major pfsync(4) Rewrite Has Been Committed
5) * 2023-07-06 Soft updates (softdep) disabled for future VFS work
6) * 2023-07-11 Wayland on OpenBSD
7) * 2023-07-12 pkg_*: the road forward
8) * 2023-07-13 OpenBGPD 8.1 released
9) * 2023-07-14 Mandatory enforcement of indirect branch targets
fenen (feed)>

The latest posts are shown at the bottom of the list (closest to the next command prompt) and unread items have a * between the number and the date.

To read a specific item, use the show command with a number again. This item will then be marked as read.

fenen (feed)> show 9
	...Entry number 9 is displayed...

You can also manually mark an item as read or unread with the mark and unmark commands, and you can open any item in the browser with the open command, all of which also take an item number. If you were just reading a post and you invoke open without a number, that post will open in the browser.

fenen (feed)> open
	...Browser opens with entry number 9...
fenen (feed)>

You'll notice that if you try using show without a number, it will print the list of entries in this feed again. To get back to the list of all feeds, you can use the show all command. Additionally, to view a list of unread feed entries across all of your feeds, use the show unread command:

fenen (all)> show unread
	...A list of unread feeds is printed...
fenen (unread)>

Finally, quit fenen with the quit command:

fenen (all)> quit


Here are a few extra tidbits to make your fenen experience more pleasant:

You don't actually have to type out full command names, you can type any of the first part of a command and it will be expanded to the full meaning. For example s u is the same as sho un which is the same as show unread.

Any command which takes an item number can also accept ranges and lists of numbers. For example, you can use delete 1,4,6-10 to delete feeds 1, 4, 6, 7, 8, 9, and 10 or mark * to mark all entries as read.

There is no space needed between a command and item numbers if you use the single-letter short form of a command. For example, you can use o1-5 to open entries 1 through 5, which is equivalent to o 1-5 and open 1-5.

This is what a typical fenen session looks like once you've got all your feeds added and are just using it to check for new entries:

fenen (all)> r
Refreshed 150/150 feeds in 42 seconds
fenen (all)> s u
1) * 2023-07-16 Ken Shirriff's blog - Undocumented 8086 instructions, explained by the microcode
2) * 2023-07-16 Phoronix - Linux Mint 21.2 Released With Cinnamon Enhancements, Other Desktop Polishing
3) * 2023-07-16 Technology Connections - Longer-lasting light bulbs: it was complicated
fenen (unread)> o1,3
fenen (unread)> m*
fenen (unread)> ^D

First we start by refreshing the feeds to pull in content that was published since the last refresh. Then we tell fenen to show us any unread entries (which, in this case, is everything that is new since the last refresh). We decide that we want to watch that Technology Connections video and read that blog post from Ken Shirriff, so we open them in the browser. Finally, we mark all of these posts as read and quit by pressing Control+D.

In order to view all the available commands, type help and hit enter. Most commands are self-explanatory, but here is a breakdown:

Command Reference

add <url>:

Adds the given feed URL <url> to fenen. This must be a link to a feed; fenen does not support finding a feed embedded in a site. Perform a refresh to load the content from that feed.

delete <n>:

Delete the given feed(s) from the feed reader. All content will be removed from fenen. This command only deletes a feed from fenen and will do nothing if you try to delete a post. If you're doing this because you want to shrink the size of fenen's database on disk, follow this up with a vacuum command.

change <n>:

Change the name and/or category of the given feed(s). You will be prompted to input a new name and category. The current feed name and category are printed between square brackets and if nothing is entered for either of the values then that value won't be changed.

(show|list|print) [all|unread] <n>:

If given the subcommand all, show the list of feeds. If given the subcommand unread, show a list of unread entries from all feeds. If not given a subcommand, then show the given item(s) which will either print out a listing of posts in a feed if run on the list of feeds, or show a specific post if run on a list of posts.

import [file]:

Import feeds from the given OPML file, adding them to fenen. If no file is given, fenen will try to import from feeds.opml.

export [file]:

Export the list of feeds in fenen to an OPML file. If no file is given, fenen will try to export to feeds.opml.


Refresh all feed content by downloading the latest version of each site's feed. This may take a while to complete if you have a slow connection many feeds.

mark <n> and unmark <n>:

Mark the given item(s) as read or unread, respectively.

open <n>:

Open the given item(s) in the browser. If the item(s) are feeds, this will open the website corresponding to that feed as long as the feed contains that information, otherwise it will open the feed itself. If the item(s) are posts, this will open those posts in the browser.

get <n>:

Download the given entry from the Internet. This can be used to read the full content of a post if only a short summary is given in the feed.


Clean up free space not currently being used to store data in the database. This simply shrinks the size of the database file on disk if there is space inside that is not being used. This command isn't needed under normal circumstances.


Print out a help message with a brief explanation of each command.


Performs a search on the current list of feeds or the current list of entries in a feed and displays the results. You can search under the name and url for feeds and under the name, url, and date for entries in a particular feed.


Exit fenen. ^D (EOF) and ^C (SIGINT) also work. Note that if you try to exit with ^C in the middle of a refresh, fenen will finish all currently ongoing downloads and then exit.


Fenen has a reasonable and small default configuration. Having a config file is not necessary unless you want to override any of the settings and, if you do, it's only necessary to put the specific option(s) you wish to override in the config file.

Fenen will first look for a custom configuration in $XDG_CONFIG_HOME/fenen.conf, then ~/.config/fenen.conf, and then ~/.fenen/fenen.conf. If none is found it will use the built-in default settings.

The following is a sample configuration file containing the built-in defaults:

# The location of the database file
db = $XDG_DATA_HOME/fenen.db

# The number of threads to use when refreshing feeds
# The sweet spot for performance seems to land between 4 and 8 threads
# depending on your hardware
threads = 4

# The program to use to open items with the `open` command
browser = firefox

# The program to use to read feed entries in fenen with the `show` command
pager = less

# The program to download full entries with the `get` command
downloader = curl -O

Development Roadmap

There are a few things I want to add to fenen before I consider it feature-complete:

  • Write a manpage
  • Add ability to download podcast/youtube video directly?
  • Tests?


Send bug reports, feedback, suggestions, and patches by email to jbauer@paritybit.ca.

I am especially interested in fixing accessibility issues.

Please make sure to format your code using Black (with default settings) before submitting a patch.

I want to keep fenen as small and external-dependency-free as is reasonable. Please keep that in mind when making suggestions or sending patches.