
Go-Brew Tea Timer
A CLI tea brewing timer built with Go and Bubble Tea — because tea deserves better than a phone alarm.
Go-Brew Tea Timer
A CLI timer for brewing tea. The good kind — one where you pick your tea type and it tells you the exact time and temperature.
I made it because every time I brew tea I either rush it or over-steep it. Tea timers exist, but they’re usually phone apps or kitchen gadgets. I wanted something that lives in my terminal and respects the same workflow I use for everything else.
The tea presets
Different teas need different times and temperatures. Go-Brew ships with presets so you don’t have to look them up:
var TeaPresets = []TeaType{
{"Rooibos", 4 * time.Minute, "95°C", lipgloss.Color("#FF6B35")},
{"Green Tea", 2 * time.Minute, "80°C", lipgloss.Color("#228B22")},
{"Black Tea", 3 * time.Minute, "95°C", lipgloss.Color("#8B4513")},
{"Herbal", 5 * time.Minute, "95°C", lipgloss.Color("#9370DB")},
{"White Tea", 2 * time.Minute, "85°C", lipgloss.Color("#F0F8FF")},
{"Oolong", 3 * time.Minute, "90°C", lipgloss.Color("#DAA520")},
}
Each preset has its own color — the UI reflects the type you’ve selected. Navigate with arrow keys, set the timer, and go.
How it works
Go-Brew follows the Model-View-Update pattern from Bubble Tea. The timer ticks once per second, updates the progress bar, and fires system notifications when done.
func (m Model) Update(msg tea.Msg, m Model) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "q", "ctrl+c":
m.quitting = true
return m, tea.Quit
case "r":
return resetTimer(m), nil
case " ":
return toggleTimer(m), m.tickCmd()
}
case TickMsg:
m.elapsed += time.Second
m.progress = float64(m.elapsed) / float64(m.duration)
if m.elapsed >= m.duration {
return timerComplete(m), m.notifyCmd()
}
return m, m.tickCmd()
}
return m, nil
}
When the timer finishes, it fires a desktop notification (Windows, macOS, Linux) and plays an audio alert. The notification library is beeep and the audio playback uses go-mp3.
The architecture
Everything lives in a few files, each with a single responsibility:
model.go— state (current tea, elapsed time, progress, etc.)view.go— renders the progress bar and state textupdate.go— theUpdatefunction: key events, ticks, notificationsconfig.go— tea presets and command-line flagsaudio.go— cross-platform audio playback
No state management library. No routing. The state is the struct, the view is a function that takes the state and returns a string, and the update function is the only place state changes happen.
Performance
The binary uses one goroutine for the timer tick and channels for message passing. Memory sits around 2–5MB, CPU under 1%. The compiled binary is about 3–5MB with no dependencies.
Usage
# Default brew time (4 minutes)
go-brew
# Custom duration
go-brew -duration 2m
# Minutes and seconds
go-brew -duration 3m30s