npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@isomoes/iread

v0.2.3

Published

Local, single-user RSS/Atom reader with a keyboard-first three-pane web UI, inspired by newsboat.

Readme

iread

A concise, local, single-user RSS/Atom reader inspired by newsboat. Keyboard-first three-pane UI, TypeScript end to end. You add feeds by URL, refresh them on demand, and read server-sanitized article bodies in a calm, terminal-quiet reading surface with light, dark, and system themes.

There is no auth, no multi-user, no cloud sync, and no background scheduler. Refresh is always user-initiated. All data lives in a single local SQLite file — by default ~/.config/iread/iread.db — which is the trust boundary.

iread UI

📺 Watch the v0.2.2 intro video on Bilibili.

Quick start

npx @isomoes/iread

Then open http://localhost:8787. Requires Node.js 24 or newer (the server uses the built-in node:sqlite module). Data is stored in ~/.config/iread/iread.db ($XDG_CONFIG_HOME is honored).

Usage: iread [options]

Options:
  -p, --port <port>  Port to listen on (default: $PORT or 8787)
      --db <path>    SQLite database file
                     (default: $DB_PATH or ~/.config/iread/iread.db)
      --opml <path>  OPML file auto-saved on every subscription change
                     (default: $OPML_PATH or feeds.opml next to the database;
                     pass "" to disable)
  -v, --version      Print the version and exit
  -h, --help         Show this help and exit

Run with Docker

Prebuilt multi-arch images (amd64 + arm64) are published to the GitHub Container Registry, so no Node.js on the host is required:

docker run -d --name iread -p 8787:8787 -v iread-data:/data ghcr.io/isomoes/iread:latest

Then open http://localhost:8787. The SQLite database and the auto-saved feeds.opml mirror live under /data in the container — here the named iread-data volume — so your subscriptions survive container upgrades.

A docker-compose.yml is included for the same thing:

docker compose up -d

Build the image from the checkout instead of pulling it with docker compose up -d --build, or docker build -t iread ..

PORT (default 8787), DB_PATH (default /data/iread.db), and OPML_PATH (default /data/feeds.opml) are configurable via -e/environment:. To store data in a host directory instead of a named volume, bind-mount it and make it writable by the image's unprivileged node user (uid 1000), e.g. mkdir iread-data && sudo chown 1000:1000 iread-data then -v "$PWD/iread-data:/data".

Features

  • Add a feed by URL (title resolved from feed metadata), delete a feed.
  • Refresh one feed (r) or refresh all feeds (R).
  • Sidebar feed list with per-feed unread counts plus global smart-view totals.
  • Smart views: All, Unread, Starred, applied across the article list, with per-feed selection.
  • Article list with read/unread indicator, source feed, relative time, and star indicator.
  • Reader pane with title, author, date, server-sanitized HTML body, and open-original link.
  • Mark read/unread, auto-mark-read on open (and, on desktop, when you move on from a viewed item), mark-all-read for the current feed or view.
  • Star and unstar.
  • Live case-insensitive search over title plus summary in the current view.
  • Full keyboard navigation as the primary interaction model.
  • OPML import (bulk add) and OPML export, plus an always-current feeds.opml auto-saved next to the database on every change for quick sharing and backup. The auto-saved file is a one-way snapshot of the database (overwritten on every change and at startup), so use it for backup and sharing — to bring feeds in, use OPML import.
  • Light, dark, and system theme, persisted to localStorage.

Usage

  1. Start the app (dev or production) and open it in your browser.
  2. Add a feed by pasting its RSS or Atom URL into the add-feed form in the sidebar, or import an OPML file from the OPML menu. A config/sample-feeds.opml file is included for a quick start, and isomoes/arch-config/iread/feeds.opml is a real-world example (the feed set isomoes actually reads) you can import directly.
  3. Select a feed or a smart view (All, Unread, Starred) in the sidebar.
  4. Navigate the article list with j and k, open an article with Enter, and read it in the right pane.
  5. Press r to refresh the current feed or R to refresh all feeds. Refresh fetches remote feeds; client polling only refreshes local data and never re-fetches remotely.
  6. Star with s, toggle read with m, mark a whole scope read with A, and search with /.
  7. Press ? at any time to see the full keyboard map. Press t to cycle the theme.

Keyboard shortcuts

Keys are case-sensitive (Shift matters). Bindings fire only when you are not typing in an input and no Ctrl, Meta, or Alt modifier is held.

| Key | Action | Effect | | ------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | j / Down | Down in focused pane | Pane-contextual: move the article selection (list), the feed/view selection (sidebar), or the reader scroll down one. Stops at the end (no wrap). | | k / Up | Up in focused pane | As j, upward. Stops at the start. | | n | Next unread | Jump to the next unread item below; wrap to the first unread from the top if none below. | | g | Top | Pane-contextual: in the list, select the first item; in the sidebar, jump to the first feed/view; in the reader, scroll the article to the top. | | G | Bottom | Pane-contextual: in the list, select the last item; in the sidebar, jump to the last feed/view; in the reader, scroll the article to the bottom. | | Enter / o | Open / focus reader | Render the selected item, mark it read, and move focus into the reader. | | J / ] | Next feed/view | Move sidebar selection down and load its items, selecting the first one. | | K / [ | Previous feed/view | Move sidebar selection up and load its items. | | m | Toggle read/unread | Flip the read state of the selected item; counts update. | | s | Toggle star | Flip the starred state; in the Starred view an unstarred item leaves the list. | | A | Mark feed/view read | Mark the current scope read and offer an Undo toast. | | r | Refresh current feed | Refresh the selected feed, or the feed of the selected article in a smart view. | | R | Refresh all feeds | Refresh every feed and update counts on completion. | | v | Open original | Open the article link in a new tab. | | # then N | Open link by number | Links in the article body are numbered inline [N]; press #, type the number, and it opens in a new tab — instantly once the number is unambiguous, otherwise Enter confirms; Esc cancels. | | / | Focus search | Focus and select the search input. | | Esc | Contextual dismiss | Close help, clear search, or return focus from the reader to the list. | | ? | Help overlay | Toggle the keybinding overlay. | | t | Toggle theme | Cycle light, dark, and system theme; persisted. |

Project layout

  • src/shared/ shared, type-only DTOs used by both server and web.
  • src/server/ Hono API, SQLite access, feed fetch/parse/sanitize, OPML, SSRF guard.
  • src/web/ React app: three-pane layout, hooks, components, styles.
  • data/ development SQLite database (gitignored); production data lives in ~/.config/iread/.

Requirements (development)

  • Node.js 24 or newer (the server uses the built-in node:sqlite module).
  • pnpm.

Install

pnpm install

Develop

pnpm dev

This runs two processes with concurrently:

  • The API server on port 8787 (tsx watch src/server/index.ts).
  • The Vite dev server on port 5173 with HMR.

Vite proxies /api to http://localhost:8787, so the browser only ever talks to 5173 and there are no CORS concerns.

In development (NODE_ENV is not production) the database defaults to data/iread.db inside the repo (gitignored), so dev experiments never touch your real ~/.config/iread data.

Open http://localhost:5173

Build and start (production)

pnpm build
pnpm start
  • pnpm build compiles the server to dist/server/ (with shared types in dist/shared/) and bundles the web app to dist/web/.
  • pnpm start runs a single Hono process that serves the API and the static web bundle with SPA fallback.

Open http://localhost:8787

PORT (default 8787), DB_PATH (default ~/.config/iread/iread.db), and OPML_PATH (default feeds.opml next to the database; set empty to disable the auto-saved mirror) are read from the environment. See config/.env.example.