Skip to content


Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?

Latest commit


Git stats


Failed to load latest commit information.
Latest commit message
Commit time

Lucerne 🏔

Lucerne (named after the Swiss city) is a Twitter reader designed to amplify my personal Twitter workflows. Lucerne is built on my usual homebrew stack, with Ink on the backend and Torus on the browser. You can read more about Lucerne on my blog.

Lucerne's home page

Lucerne is built around a new concept called channels. Channels are search filters over all of Twitter that can be pinned to the sidebar, so you can switch between them quickly and easily to "tune in" to different parts of the Twittersphere. Channels can filter to specific threads, tweeters, conversations, types of media, keywords, and engagement metrics, providing a powerful primitive that can be used to build a lot of higher level use cases for channels. Built-in actions like seeing another user's tweets or searching by hashtag also take advantage of the core filter pattern. (e.g. clicking on a profile searches for from:the_user)

Twitter and Tweetdeck have built-in search and timeline features that are similar, but UX of experimenting with filters and immediately saving the good ones to a growing list is what really makes Lucerne fun to use, and that isn't possible in existing clients.


I think tools should start simple and grow with use. Lucerne is designed around a single idea, filtered channels, with other peripheral information I frequently check on Twitter displayed on the side for reference.

In addition to the headline channels feature, Lucerne has a few other niceties designed around the way I use Twitter, as a place for finding new ideas and sharing what I'm working on with the public.

Lucerne is designed as a better Twitter experience, but isn't designed as a complete replacement. Where can provide a better experience (for authoring tweets, interacting with threads, etc.) either due to API support or edge cases, Lucerne links out to Twitter's web client.

It goes without saying, because the product is designed to help me get the most out of Twitter rather than make me stay on Twitter to consume ads, it feels much cleaner, more focused, and pleasant to use.


Lucerne allows you tu search twitter with a pattern:filter style syntax. Most of this borrows from Twitter's v1.1 search API queries, except a few that are custom and pre-processed on the backend, like re:tweet_id for replies to a tweet or sort:top to sort by top rather than recency.

Searching in Lucerne

I like a textual syntax for advanced searches over a bunch of toggles in the UI, since text queries can be copy-pasted, easily saved for later, and edited quickly on the keyboard. Using this particular syntax confers two advantages:

  1. The syntax is also mostly usable on Twitter's web client search page, which is nice when I'm using that instead of Lucerne.
  2. It keeps the implementation simple, since I pass the query (mostly) transparently through to Twitter's API endpoint.

If using Twitter's basic search API (i.e. on non-Premium, non-Enterprise developer accounts), note that their Search API is limited to the last 7 days or so. You can probably increase this limit by upgrading your developer account, but I haven't done so personally.

Account and Tweet metrics

I usually share updates to projects I'm working on or my blog posts daily on Twitter, and like to keep track of how those tweets are doing with engagement. This use case gets a panel on the right side.

My own stats -- follower and following counts -- are there because I like keeping track of it, but I need to check it at most once a couple days, and it's not important. So it gets a small footnote on the left side below the channels.

Recent followers

Below the engagement metrics panel, there's a list of new followers in reverse-chronological order, so I can see if there's anyone interesting who recently found me on the platform. I can see if anyone has a big presence on Twitter and one-click switch to viewing their recent or popular tweets.

How it works

Lucerne's backend is written purely in Ink. Lucerne reuses server and routing libraries from other Ink projects, but implements custom implementations of the SHA1 HMAC algorithm and OAuth 1.1 signature algorithm, both available in lib/. These are used to communicate with Twitter's API without relying on a third-party implementation.

(Is rolling your own crypto like this smart? Probably not. But this is a toy project, and I'm the only one who uses it.)

The frontend is written as a single page Torus application, with no other dependencies. This keeps the page fast and responsive, even on slower connections and devices.


At the moment, Lucerne is designed as a single-user application: a single server can only serve one logged-in Twitter account. To run your own Lucerne server, create a file in the root of the repository, with the following keys. You should be able to find these credentials when you register a Twitter developer account.

UserID := '<Your Twitter account's user ID, which should be a number>'
ConsumerKey := '<Your developer account's consumer public key>'
ConsumerSecret := '<Your developer account's consumer secret key>'
BearerToken := '<Your developer account's Bearer Token>'
OAuthToken := '<Your account's OAuth token>'
OAuthSecret := '<Your account's OAuth secret>'

You'll also need to modify static/js/main.js to replace const ME = 'thesephist'; with your own Twitter username.

With the file present, run ink to start the server, running on localhost:7238/. (To do this, you need to have Ink installed.)

Running with Docker

Alternatively, if you don't already have Ink installed, you can run Lucerne from a Docker container. You will need to supply your twitter credentials (as above) but as environment variables, like so .env:


Then from the root of the repo you can build and run the container using these commands:

docker build . -t lucerne
docker run -it --env-file .env -p 7238:7238 lucerne

Lucerne uses GNU Make for development scripts.

  • make or make run starts an auto-restarting development server that restarts when any relevant files are changed.
  • make fmt or make f auto-formats all Ink files with inkfmt. You need to have inkfmt installed on the system for this to work.


Lucerne runs on the hard-coded port 7238 when you run ink in the root directory of the project. To do this in deployment, I define Lucerne as a systemd service with lucerne.service and let systemd manage the server. If you want to try deploying Lucerne yourself, make sure you have a valid from above and you're invoking ink from the correct working directory.


A Twitter reader designed for learning from the Twittersphere, built with Ink and Torus








No releases published