docs: add README, gitignore, and Ollama integration tests

This commit is contained in:
2026-04-03 13:00:13 +02:00
parent 1a6aa73e48
commit 55fda5a1e3
3 changed files with 209 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
reddit-reader
*.db

132
README.md Normal file
View File

@@ -0,0 +1,132 @@
# reddit-reader
A Go TUI that monitors subreddits for interesting posts, filters them by keyword and LLM relevance scoring, generates bullet-point summaries, and presents everything in an interactive reading list.
Runs as a systemd user service for continuous monitoring. The TUI connects on launch via gRPC.
## Architecture
Single binary, three subcommands:
- `reddit-reader serve` — monitor daemon + gRPC server
- `reddit-reader tui` — interactive reading list client
- `reddit-reader setup` — first-run configuration wizard
```
Reddit API ──► Monitor ──► Keyword Filter ──► LLM Scorer ──► SQLite
TUI ◄──── gRPC ◄──── Server ◄────┘
```
## Features
- **Keyword + LLM filtering** — cheap regex/keyword pre-filter, then LLM relevance scoring against your interests
- **Local-first LLM** — Ollama/llama.cpp by default, Mistral API as fallback
- **5-bullet summaries** — generated by the LLM for posts that pass the relevance threshold
- **Feedback loop** — thumbs up/down on posts feeds back into relevance scoring as few-shot examples
- **Live streaming** — new posts push to the TUI in real-time via gRPC server-side streaming
- **Systemd integration** — user service for the daemon, socket activation for on-demand startup
## Requirements
- Go 1.26+
- A local LLM via [Ollama](https://ollama.com) (recommended) or a [Mistral API](https://mistral.ai) key
- Reddit API credentials ([script app](https://www.reddit.com/prefs/apps))
## Install
```bash
go install somegit.dev/vikingowl/reddit-reader@latest
```
Or build from source:
```bash
git clone https://somegit.dev/vikingowl/reddit-reader.git
cd reddit-reader
go build -o reddit-reader .
```
## Setup
```bash
reddit-reader setup
```
Walks you through:
1. Reddit OAuth credentials
2. LLM backend selection (auto-detects Ollama)
3. Subreddits to monitor
4. Your interests (used for relevance scoring)
5. Optional systemd unit installation
Config is stored at `~/.config/reddit-reader/config.toml`.
## Usage
Start the monitor daemon:
```bash
reddit-reader serve
```
Or enable it as a systemd user service:
```bash
systemctl --user enable --now reddit-reader.socket
systemctl --user start reddit-reader.service
```
Launch the TUI:
```bash
reddit-reader tui
```
### TUI Keybindings
| Key | Action |
|-----|--------|
| `j/k` | Navigate up/down |
| `enter` | Expand/collapse summary |
| `s` | Star post |
| `d` | Dismiss post |
| `o` | Open in browser |
| `+/-` | Vote on relevance (feeds back into scoring) |
| `tab` | Switch view (Reading List / Starred / Archive / Settings) |
| `g/G` | Jump to top/bottom |
| `q` | Quit |
## Configuration
`~/.config/reddit-reader/config.toml`:
```toml
[reddit]
client_id = "..."
client_secret = "..."
username = "..."
password = "..."
[llm]
backend = "ollama" # ollama | llamacpp | mistral
endpoint = "http://localhost:11434"
model = "ministral-3:8b"
relevance_threshold = 0.6
[interests]
description = "Go programming, Linux, NixOS, systems programming"
[monitor]
poll_interval = "2m"
max_posts_per_poll = 25
[grpc]
socket = "/run/user/1000/reddit-reader.sock"
```
All config values can be overridden with environment variables: `REDDIT_READER_REDDIT_CLIENT_ID`, `REDDIT_READER_LLM_API_KEY`, etc.
## License
MIT

View File

@@ -0,0 +1,75 @@
package llm_test
import (
"context"
"net/http"
"os"
"testing"
"time"
"somegit.dev/vikingowl/reddit-reader/internal/domain"
"somegit.dev/vikingowl/reddit-reader/internal/llm"
)
func ollamaAvailable() bool {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
req, _ := http.NewRequestWithContext(ctx, "GET", "http://localhost:11434/api/tags", nil)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return false
}
resp.Body.Close()
return resp.StatusCode == http.StatusOK
}
func TestIntegrationOllamaScore(t *testing.T) {
if os.Getenv("INTEGRATION") == "" {
t.Skip("set INTEGRATION=1 to run Ollama integration tests")
}
if !ollamaAvailable() {
t.Skip("Ollama not running at localhost:11434")
}
client := llm.NewOpenAIClient("http://localhost:11434", "ministral-3:8b")
post := domain.Post{
Title: "Go 1.26 introduces new iterator patterns for range-over-func",
SelfText: "The Go team has released version 1.26 with significant improvements to the iterator protocol. Range-over-func now supports push and pull iterators natively, eliminating the need for channel-based iteration patterns that were common before.",
}
interests := domain.Interests{
Description: "Go programming, systems programming, Linux, NixOS",
}
score, err := client.Score(context.Background(), post, interests)
if err != nil {
t.Fatalf("Score: %v", err)
}
t.Logf("Score: %.2f", score)
if score < 0.0 || score > 1.0 {
t.Errorf("score %.2f out of range [0, 1]", score)
}
}
func TestIntegrationOllamaSummarize(t *testing.T) {
if os.Getenv("INTEGRATION") == "" {
t.Skip("set INTEGRATION=1 to run Ollama integration tests")
}
if !ollamaAvailable() {
t.Skip("Ollama not running at localhost:11434")
}
client := llm.NewOpenAIClient("http://localhost:11434", "ministral-3:8b")
post := domain.Post{
Title: "Systemd 256 brings major changes to socket activation",
SelfText: "The latest systemd release includes reworked socket activation logic, new unit file directives for resource management, improved journal performance, and better container integration. The socket activation changes affect how services handle inherited file descriptors, with a new API for querying activation state. Container support now includes native OCI image pulling and integrated rootless operation. The journal subsystem saw a 40% improvement in write throughput through batched fsync operations.",
}
summary, err := client.Summarize(context.Background(), post)
if err != nil {
t.Fatalf("Summarize: %v", err)
}
t.Logf("Summary:\n%s", summary)
if len(summary) < 20 {
t.Error("summary too short")
}
}