all repos — llm_aggregator @ 4b4b37a236f75a04bd530d0a4ffaa1fb26b89a27

A CLI tool to aggregate RSS feeds and summarise them with LLMs

CHANGELOG.md (view raw)

  1# Changelog
  2
  3All notable changes to this project will be documented in this file.
  4
  5The format is based on [Keep a
  6Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to
  7[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
  8
  9## [1.0.0] - 2026-04-27
 10
 11### Added
 12
 13- Linting and code quality tooling via `golangci-lint`.
 14
 15### Fixed
 16
 17- Tight CPU-spinning polling loop in signal handler (added `time.Sleep` to
 18  avoid busy-waiting).
 19- `--temperature 0` was silently overwritten by the default (0.7); temperature
 20  is now a pointer so `nil` means "not provided" and a non-nil value — including
 21  `0.0` — means "explicitly set".
 22- `WasInterrupted` godoc comment capitalised to satisfy `golint`.
 23
 24## [0.15.0] - 2026-04-27
 25
 26### Added
 27
 28- **`--timeout N` flag**: LLM request timeout in seconds (default: 300). If the
 29  LLM API call takes longer than this, the request is aborted and a descriptive
 30  error is returned. The timeout is implemented as a `context.WithTimeout`
 31  derived from the signal-handling context, so both signal interrupts and
 32  timeouts abort the call.
 33- **`DefaultLLMTimeout` constant**: Centralises the default timeout value
 34  (300 seconds) in `internal/defaults`.
 35- **LLM client unit tests**: Three tests for the constructor: timeout default,
 36  all-parameter defaults, and API key validation.
 37
 38### Changed
 39
 40- **`LLMClient.llmTimeout` field**: Renamed from `timeoutSeconds` for clarity.
 41  The field stores the timeout in seconds; 0 means no timeout.
 42
 43## [0.14.0] - 2026-04-27
 44
 45### Added
 46
 47- **Signal handling for graceful shutdown**: `llm_aggregator` now handles
 48  `SIGINT`, `SIGTERM`, and `SIGHUP`. When a signal arrives during execution:
 49  - The context is cancelled, stopping in-progress operations as soon as possible
 50  - Any partial LLM summary already received is written to stdout (or the output file)
 51  - The process exits with code 130 (128 + signal number) instead of a generic error
 52  - In TUI mode, `q`/`Ctrl+C` also triggers clean shutdown via `RequestExit()`
 53- **`Runtime.Interrupted` and `Runtime.WasInterrupted()`**: New state field and
 54  query method allow callers to detect signal-caused termination.
 55
 56## [0.13.0] - 2026-04-26
 57
 58### Added
 59
 60- **`--stdin` flag**: Read raw RSS/Atom XML directly from standard input,
 61  composable with `-f/--feeds-file` to collate articles from both sources into
 62  a single LLM summary. Stdin articles appear before feeds-file articles in the
 63  collated output.
 64  - `curl -s $URL | llm_aggregator --stdin -p "Summarise"`
 65  - `llm_aggregator -f feeds.txt --stdin -p "Summarise all"`
 66  - Environment variable: `LLM_AGGREGATOR_STDIN`
 67- **`--feeds-file` is no longer required**: Either `--feeds-file` or `--stdin`
 68  (or both) must be provided. A clear error is returned if neither is specified.
 69
 70### Changed
 71
 72- Refactored `ParseFeedsFromFile` to delegate to a new `ParseFeedFromReader`
 73  method that accepts any `io.Reader`, enabling both stdin and unit test usage
 74  with `strings.Reader`.
 75
 76## [0.12.0] - 2026-04-26
 77
 78### Added
 79
 80- **`-P/--plain` flag**: Output the raw LLM summary without any formatting or
 81  metadata. Useful when the summary is to be consumed by downstream scripts.
 82
 83## [0.11.0] - 2026-04-23
 84
 85- Progress bar now tracks estimated token count (via tiktoken) before sending
 86  to LLM, then updates with actual completion tokens from the API response.
 87  Gives a more accurate picture of how much content is being sent.
 88
 89## [0.10.0] - 2026-04-23
 90
 91### Added
 92
 93- **Short command-line flags**: Single-character shortcuts for the most
 94  commonly used options:
 95  - `-f` / `--feeds-file` — feeds file path
 96  - `-p` / `--prompt` — summarisation prompt
 97  - `-n` / `--max-articles-per-feed` — articles per feed limit
 98  - `-d` / `--max-days-old` — article age filter
 99  - `-i` / `--include-keywords` — keyword include filter
100  - `-e` / `--exclude-keywords` — keyword exclude filter
101  - `-m` / `--model` — LLM model selection
102  - `-o` / `--output` — output format
103  - `-t` / `--tui` — enable TUI
104  - `-D` / `--dry-run` — dry run mode
105- **Styled help output**: Custom lipgloss-styled help text at
106  `internal/cli/help.go` replaces go-arg's default plain-text output. Flags are
107  rendered in cyan, section headers in bold, with consistent column alignment.
108  Respects `$NO_COLOR`.
109
110## [0.9.0] - 2026-04-23
111
112### Added
113
114- **Markdown rendering in TUI**: Implemented `charmbracelet/glamour` to
115  render LLM summary output with proper markdown styling. Headers, bold,
116  italic, code blocks, lists, and other markdown elements are now styled
117  for better readability in the terminal.
118- **Formalised infobar keybinds**: TUI keybinds are now defined as
119  dedicated style variables at the top of the model for easier maintenance.
120  Keybinds are bold while separators and action text are subtle but
121  distinguishable.
122- **Thinking indicator as separate element**: The `[💭 Thinking: ON/OFF]`
123  toggle indicator is now on its own line above the keybinds, using a
124  distinct style (magenta+bold when ON, subtle+italic when OFF).
125- **Dynamic text wrapping on resize**: TUI summary text now re-wraps when
126  the terminal window size or font changes, with proper markdown re-rendering.
127
128## [0.8.0] - 2026-04-23
129
130### Added
131
132- **Thinking tags toggle in TUI**: LLMs that output `<think>`/`</think>`
133  thinking tags now have those sections automatically removed from the summary
134  display by default. Users can press `t` to toggle the thinking section back
135  on, displayed in gray at the head of the output with XML tags stripped for
136  cleaner presentation. A status indicator shows whether thinking is currently
137  visible.
138
139## [0.7.1] - 2026-04-22
140
141### Fixed
142
143- Environment variables were correctly overriding config file values, but CLI
144  arguments without explicit flags were also overriding config file values
145  unintentionally.
146  - Root cause: `go-arg` was populating struct fields with default values
147    from `default:` tags even when those CLI flags weren't provided
148  - Fix: Removed `default:` tags from Args struct fields for LLM options
149    (`model`, `base-url`, `api-key`, `max-tokens`, `temperature`);
150    Viper's own default handling is sufficient
151  - Fix: Changed Args struct fields from value types to pointer types
152    (`*string`, `*int`, `*float64`) so that "not provided on CLI" can be
153    distinguished from "provided but zero/empty"
154  - Fix: Updated `BindCLIArgs()` and `isZero()` to properly handle pointer
155    types and distinguish nil pointers from zero values
156- `isZero()` with compound type switch cases: Compound type cases like `case
157  *int, *int8, *int16, *int32, *int64` don't properly handle nil comparison when
158  stored in an interface variable. Fixed by splitting into individual type cases.
159- `isZero()` missing nil interface handling: When a nil pointer is stored
160  in an `any` interface, `value != nil` returns `true` (the interface itself
161  is not nil). Added early `if v == nil { return true }` check.
162
163## [0.7.0] - 2026-04-22
164
165### Added
166
167- **`--dry-run` option**: Validate configuration and preview what would happen
168  without making any LLM API calls. Useful for validating feeds file and
169  configuration, checking article counts and filtering results and anything
170  else that can be done "offline".
171- **Styled terminal output using `lipgloss`**: All CLI output now uses ANSI
172  colour codes for better readability:
173- **`$NO_COLOR` environment variable support**: Respects the ["no
174color"](https://no-color.org/) standard. When `NO_COLOR` is set, all styling is
175disabled and plain text is output instead.
176
177### Changed
178
179- API key validation now only occurs when not using `--dry-run` mode
180
181## [0.6.0] - 2026-04-22
182
183### Added
184
185- **Comprehensive unit test suite**, including CLI argument tests
186  (`--feeds-file`, `--prompt`, `--api-key`, `--model`, `--base-url`),
187  configuration tests, aggregator tests, output formatter tests, processor
188  tests and defaults tests.
189- **`--base-url` CLI option**: configure custom API endpoints for different LLM
190  providers. Should support any OpenAI-compatible provider.
191  - Environment variable: `LLM_AGGREGATOR_BASE_URL`
192- `internal/defaults` package
193- `docs/TESTING.md` which contains a comprehensive testing guide with test
194  descriptions
195
196### Changed
197
198- **Configuration defaults**: All packages now use `defaults.Default*` constants
199  - `internal/config`: Uses `defaults.Default*` for default values
200  - `internal/runtime`: Uses `defaults.Default*` for runtime defaults
201  - `internal/llm`: Uses `defaults.Default*` for LLM client defaults
202
203### Fixed
204
205- `nil` pointer safety in aggregator
206  - Added nil checks for `progressCtx` across all log statements
207  - `FeedAggregator` now works correctly when progress context is nil
208  - Tests use `NewFeedAggregatorWithProgress` with explicit progress context
209
210## [0.5.0] - 2026-04-22
211
212### Added
213
214- **Accurate token counting with tiktoken**
215  - New `internal/tokeniser` package using OpenAI's tiktoken-go library
216  - BPE tokenisation for accurate token counting vs rough character estimation
217  - Encoding caching to avoid repeated initialisation overhead
218  - Model-aware encoding selection (cl100k_base, o200k_base, etc.)
219  - `CountTokens()` and `CountMessagesTokens()` functions
220  - Fallback to rough estimation when tiktoken initialisation fails
221- **Concurrent feed fetching**: Feeds are now fetched concurrently using
222  `golang.org/x/sync/errgroup`
223  - Semaphore limits concurrent requests to 10 to avoid server overload
224  - Mutex-protected shared state for thread-safe article collection
225  - Partial failures don't stop other feeds from being processed
226- **Scrollable summary in TUI**
227  - Bubble Tea viewport component for browsing long summaries
228  - Keyboard navigation: j/k or arrows (scroll), Space/B (page), g/G
229  (start/end)
230  - Mouse wheel scrolling support
231  - Scroll progress indicator showing position and total lines
232
233### Changed
234
235- **Streamlined configuration management**: Viper now uses global instance with
236automatic precedence handling. Simplified configuration flow: Parse → GetViper
237→ BindCLIArgs → ViperToRuntime
238- **TUI colour scheme**: All hex colour codes replaced with ANSI 256-colour
239  palette
240
241### Fixed
242
243- Viewport now properly handles scroll events via `Update()`. Summary content
244  pre-wrapped to viewport width before display
245- Fixed string literal colour names to use actual variables
246
247## [0.4.0] - 2026-04-21
248
249### Changed 
250
251- **TUI is now fully functional and production-ready**: Replaced the
252work-in-progress TUI implementation with a robust Bubble Tea command pattern:
253  - Runtime execution now uses native `tea.Cmd` instead of manual goroutine
254  orchestration.
255  - Added real-time progress updates via `progress.Progress` interface with
256  stage, substage, and article count messages.
257  - TUI displays a spinner, elapsed time, and final summary inline.
258  - Removed signal handling and channel synchronisation in favour of Bubble
259  Tea's built-in lifecycle.
260  - `TUIProgress` now only sends messages to an existing program, simplifying
261  integration.
262  - Both verbose mode (`SimpleLogger`) and TUI mode share the same
263  `progress.Progress` interface.
264
265## [0.3.0] - 2026-04-21
266
267### Added
268
269- **Configuration file support via TOML** (XDG-compliant and cross-platform)
270  - Load settings from `~/.config/llm_aggregator/config.toml`
271  - Supports all aggregation, API, and output options
272  - Example configuration file at `configs/config.example.toml`
273- **Environment variable support with `LLM_AGGREGATOR_` prefix**
274  - All configuration options can be set via environment variables
275  - Precedence order: CLI arguments > environment variables > config file >
276  built‑in defaults
277- New `internal/config` package with Viper integration
278  - `Load()` and `Save()` methods for configuration management
279  - `GetConfigPath()` for XDG‑compliant config file location
280  - `ConfigExists()` to check for existing configuration
281- Comprehensive unit tests for configuration loading, saving, and environment
282  variable precedence (`internal/config/config_test.go`)
283
284### Changed
285
286- **Environment variable renamed:** `DEEPSEEK_API_KEY`287  `LLM_AGGREGATOR_API_KEY`
288- **CLI help text** now reflects the new API key environment variable and
289  general configuration options
290- **Command‑line argument precedence** implemented in `cmd/llm_aggregator.go`
291via `applyConfiguration()`
292- Updated all help text, documentation, and code references
293- `README.md` completely revised with a new "Configuration" section
294
295### Removed
296
297- Global `QuietMode` and `VerboseMode` variables from
298`internal/config/config.go`. Replaced by runtime‑specific configuration in
299`runtime.Runtime` and CLI arguments
300- Hardcoded system prompt in `runtime.NewRuntime()`. Now left empty, allowing
301configuration or client default to be used
302- CLI flags now correctly override config file and environment variables
303
304## [0.2.0] - 2026-04-21
305
306### Changed
307
308- Moved all progress and status messages behind `-v/--verbose` flag
309- Default CLI mode is now silent except for final output and errors
310- Components (aggregator, processor, LLM client) use logger interface
311controlled by verbose flag
312
313## [0.1.0] - 2026-04-21
314
315### Added
316
317- Complete translation from Python prototype to Go implementation
318- RSS/Atom feed parsing using `gofeed` library
319- DeepSeek API integration via `openai-go` client, configured for `/chat/completions` endpoint
320- Content filtering and processing with keyword‑based include/exclude, date filtering, and sorting
321- Command‑line interface using `go‑arg` with automatic help and version flags
322- Multiple output formats: plain text, GitHub‑flavoured markdown, and JSON
323- Terminal user interface (TUI) built with `bubbletea` featuring:
324  - Animated progress bar with gradient colours (`#FF7CCB` to `#FDFF8C`)
325  - Live article counters (aggregated/processed)
326  - Elapsed time display
327  - Coloured status indicators using `lipgloss` styling
328  - Keyboard controls (q/Ctrl+C to quit)
329- Web content extraction fallback using `goquery` when feed descriptions are minimal
330- Configurable limits: articles per feed, maximum age, total articles
331- Token estimation and API usage logging
332- Environment variable support (`LLM_AGGREGATOR_API_KEY`) for authentication
333- Example feeds file with technology, programming, and free software sources
334
335### Changed
336
337- Program structure organised into standard Go layout: `cmd/`, `internal/`, `pkg/`
338- English spelling conventions maintained throughout (colour, initialise, summarise)
339- Error handling improved with specific messages for common API failures (invalid key, rate limits, etc.)
340- Progress reporting unified through a `ProgressContext` interface for both TUI and CLI modes
341
342### Fixed
343
344- Initial API endpoint mismatch (using `/responses` instead of `/chat/completions`)
345- Nil pointer dereferences when handling optional feed metadata (author, dates)
346- String repetition syntax errors in Go (replaced `"="*80` with `strings.Repeat`)
347- CLI help/version flag handling to show information without requiring other arguments
348- Type compatibility issues with `openai-go` v3 API (message parameters, token usage fields)