- Includes an interactive text/cli interface, and a conventional CLI
- Telegram Chat Bot. @fasolaMintesBot
- Non-interactive CLI/Markdown interface.
As someone with an Android phone--and an utter nerd--I've wanted to have better access to the Sacred Harp Minutes data, in the past few years. More up to date. Weird questies. Taking the work Mark has done with the minutes data for Sacred Harp singing, I wanted to put together a library/app(lication) for working with the minutes data at a (potentially) higher level.
As a software engineer, I also mostly end up spending time writing software that--by its very nature--I don't really use, so it's been nice to work on something that I'll actually use. It's also fun to be able to make this data accessible in a way that doesn't involve writing queries directly against the database.
This is entirely for fun, and made possible by Mark's fasolaminutes_parsing (and of course everyone who takes minutes and sings Sacred Harp).
Allowing to the fact that this isn't running on a phone and doesn't need to be downloaded over a mobile network, it runs some queries that would either take too long or require storing the data in a way that wouldn't otherwise be viable. The queries are bordering on the absurd and to make (most) operations fast, I've done some wacky database that take up a bunch of (temporary) on the system it runs on. It does many of the things that the Phone Minutes App does, with some extra things: Some of the highlights:
-
singing buddies: who have you been at the most singings with.
-
(surprising?) singing strangers: who have you never sung with (but who have sung with most of the people that you've sung with!)
-
singers' "connectedness" or the percentage of total singers that you've been at singings with.
-
unfamiliar hits: the most popular songs that are underrepresented at the singings you've been at.
-
never sung: songs that have never been sung at a singing you've been at.
-
locally popular: the most popular songs in your geographic region (state, etc.).
-
popular in your experience: songs that have been sung at singings you've been to, ordered by experience.
-
leading role models: the list if your most frequent leads with the most frequent leaders of those songs added in.
-
leader share: the percentage of all of the leads in the database that a specific leader has led (and a list of all leaders ordered by number of leads, with the corresponding cumulative percentage.)
... and more.
There's a command line interface with a fuzzy-search interface, as well as the ability to generate markdown files with "reports," for every query. Coming soon:
-
packing/delivering the application in more ways various ways so that more people can use it.
-
some improvements to the fuzzy search experience to make it feel more like an application and less like a weird command line thing.
-
more queries and views!
-
Why
odem? I wanted to name the program after a sacred harp song, but I have projects named afterjasper(public,) andsardis(private). I mostly chose it because it has short and it's easy to type (letters are split between each hand on a qwerty keyboard.) -
Why a CLI? Most of my computing work happens in CLI and adjacent contexts, so this makes sense for me and how I use computers. Also broadly speaking, I'm much more "build a CLI" and less of "make an app" or "make a website" kind of software engineer.
The rest of this file contains a bunch of technical details about the program, running/developing the software. Feel free to stop here.
Having said that, I don't think you have to be a programmer to use
odem, and I've tried to be detailed in the documentation and
instructions below for installing and getting started, so feel free to
stick around. 😃
The application is a fairly modular and largely standard Go
application. The database (sqlite) is pulled from Mark's repository
(as a git submodule) and attached to the binary, and then written out
to your temporary directory (e.g. /tmp) when the program runs for
the first time/as needed. When initializing the database, odem adds
several indexes, (non-materialized) views, and materialized
projections to optimize and facilitate some the applications more
unique queries. The database bootstrapping takes a bit under 90
seconds (on my not very impressive laptop,) so be ready for that.
Bootstrapping notwithstanding, the application is (relatively speaking,) very self contained: there's one file, it does its own setup and it will rebuild the database after upgrades. The fuzzy search interfaces make it much easier than your average CLI.
I plan to have pre-built artifacts at some point in the future if pepole are interested in running it themselves, in the mean time, if you're not used to this kind of thing, this might be a lot. Apologies...
-
You need to install Go. Do that according to these direction.
-
You need to have a working terminal with git. I haven't tested this on Macs but it should be fine. There's nothing keeping this from working on Windows, particularly WSL, but I know less of the details.
Open the terminal and run the
gitcommand. If it prints a bunch of stuff that starts withusagethen you're good. -
Clone the repository and the submodule with the following command:
git clone https://github.com/tychoish/odem.git --recursive-submodules
The
--recursive-submodulesoption downloads the database from Mark's repository and gets it all set up. If you omit this option above or clone the repository in another way, and you don't have the datebase (runls pkg/db/fasoladbto check), you can setup the submodule with:git submodule sync git submodule update
-
Build the binary. Use the following command to build the application:
go build ./cmd/odem.go
At this point, you can run the program:
./odem --help ./odem fzf --help ./odem report --help ./odem navigation --help
The first time you actually access the data, the application will write out the database. This will also happen any time your temporary space is cleared (typically on reboot), you upgrade (e.g. run
go buildas above.) -
Optional: Make
odemaccessible from any shell/terminal. I prefer to do this by creating a symbolic link in/usr/local/bin/to theodemartifact in this directory. There are other ways, and this is optional but:sudo ln -s $(pwd)/odem /usr/local/bin/odemNow from any prompt on your system (assuming
/usr/local/binis in your (search)$PATH), you can run theodemcommand and everything should work.This only needs to be done once. If it doesn't work your link may not be in the search path (or the build may not exist). Run
echo $PATHto see the list (:separated) of paths. You can add/usr/local/binto your search(for the local session only) with:export PATH="$PATH:/usr/local/bin/"
The procedure to make this permanent is probably beyond the scope of this document, and depending on your system and configuration.
At some point soon, hopefully, I'll have an installation procedure with fewer sharp edges.
-
You can write the database to the temporary space explicitly:
odem setup
If you want to delete the database, the following operation works:
odem setup reset
Let's start with a guide to the codebase:
-
cmd/ep/the entry points for various subcommands. Theodem.gofile only registers the top-most level of subcommands: for the most part thecmdrpackage makes it possible to have the barest minimum of CLI handling code. There are some helper functions (with macro vibes) inpkg/infra/for the moment. -
pkg/dbhas all of the more complex queries. All functions take parameters, and returniter.Seq2[T, error]type iterators. Queries are function-scoped constants. Thepkg/db/fs.gofile contains the database setup, initialization, and management of local database. -
pkg/modelsincludes data types generated from the database usingsqlc(andsqlcgenerated queries.) Theusr.gofile contains hand-generated models in support of complex queries. Methods on these types control some over the rendering of data in other forms. -
odemusessqlcfor only two purposes:-
Some simple "Find One"-type queries, but mostly...
-
To generate Go models from the database schema, and related views. The
pkg/db/views.sqlfile, contains a number of additional views (which sqlc` will generate a type for.)
Most of the queries are handled in the
pkg/dbpackage. -
-
for the CLI handling the
pkg/dispatch/contains the registry and dispatcher for command line entry points, with the rendering of the reports in thepkg/reportui. User input is mostly handled by theselectorpackage. -
other interfaces like MCP (
pkg/mcpsrv), a telegram bot (pkg/tgbot) and a menu-driven terminal interface (pkg/navigator) use much of the same infrastructure to present data in different ways. -
pkg/releasehandles some project automation and build tooling, and is mostly an experiment to see what it's like to fully bake build/release/deployment code into an application and avoid the otherwise inevitable collection of makefiles/build scripts/sticky tape.
Because it's 2026, there's some scaffolding for using AI coding tools/agents, and I've been using this project as a playground for exploring how to build a reasonable/maintainable piece of software somewhere between "entirely hand coded" and "only vibes."
Agents have written many of the queries, and are responsible for much of the database optimization, and test code. The architecture and code organization was manual.
The process of adding a new query is characterized as an agent skill
as add-minutes-query. If you direct the agent to use this skill with
a tight/clear description of the analysis, it does a reasonable job of
producing a new operation.
Additionally, I've set up a number of MCP servers that I find useful,
generally: sqlite adapters to make it easier to query data from the
database; gh cli tool wrappers for github stuff; gopls to use the
LSP server to facilitate code editing (with a preference for using
shared instances of gopls; as well as ripgrep and godoc for code
discovery.
In genreal my take, for the moment, is that agents are great for:
-
writing tests, both during development and when fixing bugs to prevent future regressions.
-
characterizing and fixing bugs, particularly the ones that are rooted in bad queries, and sometimes ones introduced by manual refactors.
-
writing deterministic tools, typically somewhat collaboratively with an organic programmer.
In persuit of this, I tend to scrutinize anything written by Agents, and also typically work in a fairly bottom up style, and I tend to decompose problems to some extent before delegating them to an agent. While I have long been a skeptic about coding agents, and AI in general, these tools do a pretty good job of generating SQL queries, and for developing a robust test suite: at the same time their ability to "design"/generate a reasonable architecture or interfaces is somewhat suspect.
The most effective things have been: short prompts that point out prototypes or patterns that you want to use, including pointers to where code for various components or connections lives or how it should be structured, and how you want tests to be written.
This project uses a collection of other tools and libraries that I've written that aren't common in Go programs outside of stuff I work on, in particular:
-
github.com/tychoish/fun, which is a collection of generic container types, function-object-wrappers, error collection, and some ergonomic programming tools. It's opinionated and a bit quirky.
In particular the
irtlibrary provides tooling for working with go's (newish) native iterator types. This leads to a programming style that's a bit more "lispy" and lazy/iterator driven, and function-centered than the mainline go conventions.Also the
strutpackage (for string utilities) package provides some higher-level interaces aroundstrings.Builder,bytes.Buffer, (andMutablefor providing a pooled, string-ish type around a[]byteslice.) This all makes building text and string output more ergonomic and higher-level. -
github.com/tychoish/dbx, is an elaboration on the
go-simpler.org/querieslibrary, with expanded support for decoding database-tuples into go types and a more optimized query builder. -
I've also used github.com/tychoish/grip, a structured loggong tool and github.com/tychoish/cmdr a CLI argument/subcommand framework/builder, but both are older projects of mine that have been largely superseded by other tools/approaches.
This is all very rough, and mostly to provide a vague idea of the kinds of things I'm interested in doing.
Additional UIs:
-
MCP serer (why not?) -
Telegram Bot (for remote access?) and fun. -
Minutes data guessing games (for road trips.)
-
mostly for giggles, I'd like to see about building an emacs wrapper around this using consult and marginalia maybe with a little tabular mode? 🤷 🖥️
Other features:
-
Super-locality queries (e.g. multi-state regions.)
-
apply "active singers" (activity in N-years,) filters to more queries.
-
ratio of leads of a particular song to the leaders set of leads.
Improvements:
-
some query parameters are not particularly exposed to users, could improve this.
-
while the fuzzy search tool is pretty good, it's slightly less flexible in some ways than I'd like, and I might explore replacing the library that provides this facility.
Definitely down to have other folks play with/add to/this... I'm particularly interested in this being a jumping off point for singers who are interested in programming/software/data things but who don't think of themselves as programmers. Let's talk about it!
Looking forward to singing with you all soon!