docs: improve README and create USAGE.md - Create a `USAGE.md` file to lighten the README - Create a `BUILD.md` with detailed build instructions. - Revise `README.md` to be more concise and punchy
Maxwell Jensen 85795372+maxwelljens@users.noreply.github.com
Mon, 27 Apr 2026 12:26:33 +0200
3 files changed,
488 insertions(+),
205 deletions(-)
M
README.md
→
README.md
@@ -3,7 +3,7 @@
<p align="center"> <img src="assets/logo.svg" alt="LLM Aggregator logo" width=500> <br> - <strong>A CLI tool to aggregate RSS feeds and summarise them with LLMs</strong> + <strong>Aggregate RSS feeds and summarise them with LLMs</strong> </p> @@ -11,237 +11,102 @@ 
--- -## What is `llm_aggregator`? +## What is it? -`llm_aggregator` is a command‑line utility that fetches articles from multiple -RSS feeds, filters and processes the content, and sends it to an LLM through -OpenAI-compatible API to generate a concise summary or analysis. It’s designed -for keeping up with news and articles from your favourite sources without -having to read dozens or hundreds of individual posts. +`llm_aggregator` fetches articles from multiple RSS/Atom feeds, filters and +processes the content, and sends it to any OpenAI-compatible LLM to produce a +concise summary, without reading dozens of posts. -## How do I use `llm_aggregator`? +**Supports**: RSS 2.0, Atom, JSON Feed | OpenAI-compatible APIs | Local LLMs +(Ollama, etc.) | TUI with live progress | Text/Markdown/JSON output - llm_aggregator --feeds-file FEEDS-FILE --prompt PROMPT [OPTIONS]... +--- -By default, `llm_aggregator` reads a list of RSS feed URLs, fetches the -articles, filters them by date and keywords, and sends a summary request to -the LLM. The resulting output is printed to the terminal in your chosen format -(text, markdown, or JSON). - -### Basic Options - - -f, --feeds-file FILE Path to file containing RSS feed URLs (one per line) - --stdin Read a single RSS/Atom feed from stdin (combinable with -f) - -p, --prompt PROMPT User prompt for summarisation/analysis [required] - --api-key KEY API key (default: read from $LLM_AGGREGATOR_API_KEY) - -m, --model MODEL Model to use (default: deepseek-chat) - --base-url URL API base URL (default: https://api.deepseek.com) - --max-tokens N Maximum tokens in response (default: 4000) - -P, --plain Output only the raw LLM response without formatting or metadata - -o, --output FORMAT Output format: text, markdown, or json (default: text) - --output-file FILE Write output to FILE instead of STDOUT - -t, --tui Enable TUI interface with progress bar - -D, --dry-run Validate config, show article statistics, and exit without making LLM API calls - -v, --verbose Enable verbose logging - -h, --help Show this help message and exit - --version Show version information and exit - -### Filtering & Processing - - -n, --max-articles-per-feed N Maximum articles to fetch from each feed (default: 10) - -d, --max-days-old N Only include articles from the last N days (0 for all) (default: 7) - --max-total-articles N Maximum total articles to process (default: 20) - -i, --include-keywords LIST Comma-separated list of keywords to include (case‑insensitive) - -e, --exclude-keywords LIST Comma-separated list of keywords to exclude (case‑insensitive) - --include-articles Include original articles in JSON output - -### LLM Configuration - - --temperature VALUE Sampling temperature (0.0 to 1.0) (default: 0.7) - --timeout N LLM request timeout in seconds (default: 300) - --system-prompt TEXT Custom system prompt for LLM - -### Examples +## Quick start ```bash -# Basic usage: summarise tech news from a list of feeds -llm_aggregator -f feeds.txt -p "What are the latest AI-related trends in free software?" - -# Read RSS feed directly from stdin -curl -s https://example.com/feed.xml | llm_aggregator --stdin -p "Summarise" - -# Combine feeds file with stdin feed -llm_aggregator -f feeds.txt --stdin -p "Summarise all" - -# With TUI progress bar -llm_aggregator -f feeds.txt -p "Summarise tech news" -t - -# Output to a JSON file with included articles -llm_aggregator -f feeds.txt -p "Analyse AI developments" \ - -o json --output-file analysis.json --include-articles +# Install +go install github.com/maxwelljensen/llm_aggregator/cmd/llm_aggregator.go@latest +# Create a feeds file +cat > feeds.txt << 'EOF' +https://news.ycombinator.com/rss +https://lwn.net/headlines/newrss +EOF -# Filter by keywords (only include articles about Linux or open source) -llm_aggregator -f feeds.txt -p "Linux news" \ - -i linux,opensource -d 3 +# Run +llm_aggregator -f feeds.txt -p "What are the top tech stories today?" +``` -# Use a custom model and higher token limit -llm_aggregator -f feeds.txt -p "Code analysis" \ - -m deepseek-reasoner --max-tokens 8000 +**First run?** See [docs/USAGE.md](docs/USAGE.md) for installation, configuration, +and all available options. -# Use a custom API endpoint (e.g., local Ollama) -llm_aggregator -f feeds.txt -p "Summarise news" \ - --base-url "http://localhost:11434/v1" -m llama3 +--- -# Show version information -llm_aggregator --version +## Key features -# Show help message -llm_aggregator --help -``` +| | | +|--|--| +| 🚀 | Concurrent feed fetching with rate limiting | +| 🔍 | Keyword filtering (include/exclude, case‑insensitive) | +| 📅 | Date‑based filtering and sorting (date/title/source) | +| 🤖 | Any OpenAI-compatible API (Deepseek, Ollama, OpenRouter, …) | +| 🖥️ | Interactive TUI with progress bar and mouse scrolling | +| 📦 | Config file, environment variables, and CLI flags | +| 📄 | Read feeds directly from stdin via `--stdin` | +| 🔧 | Dry‑run mode to validate config without API calls | -## How does `llm_aggregator` work? +--- -`llm_aggregator` performs the following steps for each run: +## TUI mode -1. Parse command‑line arguments -2. Read the feeds file: a plain text file containing one RSS/Atom feed URL per - line. -3. **Fetch and parse feeds concurrently**: RSS, Atom, and JSON Feed formats are - supported. Feeds are fetched in parallel with rate limiting to maximise - throughput while avoiding server overload. -4. **Extract article content**: for each feed entry, the tool extracts the - title, link, publication date, author, and description. If the feed provides - only a snippet, it can optionally fetch the full webpage using `goquery` to - extract the main content. -5. **Filter and sort articles**: articles are filtered by age (configurable - with `--max-days-old`), optionally filtered by keywords (include/exclude), - and sorted by date, title, or source. -6. Prepare the prompt with selected articles, formatted into a context - string that is sent to the LLM along with the user’s custom prompt. -7. Call the OpenAI API via the `openai‑go` client. -8. **Format and output the result**: the AI’s response is printed in the chosen - format (plain text, GitHub‑flavoured markdown, or JSON). If JSON output is - selected, the original articles can be included alongside the summary. +Enable the TUI with `-t` for a colourful progress bar, live article counters, +and elapsed time. The TUI renders LLM output as styled Markdown (headers, +bold, code blocks, lists) and supports keyboard navigation (j/k, arrows, b, +g/G) and mouse wheel scrolling.  -When the `--tui` flag is used, the entire process is wrapped in a `bubbletea` -TUI that shows a colourful progress bar, live article counters, and elapsed -time. The TUI renders Markdown content from the LLM with proper styling for -headers, bold, italic, code blocks, and lists. The TUI supports keyboard -navigation (j/k, arrows, space, b, g/G) and mouse wheel scrolling for -browsing long summaries. +--- -Feeds are fetched concurrently for optimal performance, with rate limiting to -avoid overwhelming feed servers. +## Architecture -## Configuration +``` +Feeds file / stdin + ↓ + ┌─────────────┐ + │ aggregator │ RSS/Atom/JSON Feed parsing, concurrent fetching + └─────────────┘ + ↓ + ┌─────────────┐ + │ processor │ Filter by keywords/age, sort, truncate + └─────────────┘ + ↓ + ┌─────────────┐ + │ llm │ OpenAI-compatible API call + └─────────────┘ + ↓ + ┌─────────────┐ + │ output │ Text / Markdown / JSON + └─────────────┘ +``` -`llm_aggregator` supports multiple configuration sources with the following precedence order (highest to lowest): +## Configuration precedence -1. **Command‑line arguments** – Override everything -2. **Environment variables** – Start with `LLM_AGGREGATOR_` prefix -3. **Configuration file** – `~/.config/llm_aggregator/config.toml` -4. **Built‑in defaults** - -### Configuration file - -Create a TOML file at `~/.config/llm_aggregator/config.toml` with the following structure: - -```toml -# Feed aggregation options -max_articles_per_feed = 10 -max_days_old = 7 -max_total_articles = 20 - -# Content filtering (comma-separated keywords) -# include_keywords = "linux,opensource" -# exclude_keywords = "windows,microsoft" - -# LLM API options -# api_key = "your_api_key_here" # Can also be set via LLM_AGGREGATOR_API_KEY env var -# base_url = "https://api.deepseek.com" # Optional custom API endpoint -model = "deepseek-chat" -max_tokens = 4000 -temperature = 0.7 - -# System prompt for LLM API -system_prompt = """You are an expert analyst and summariser. -You analyse content from multiple sources and provide -concise, insightful summaries based on user requests. -Focus on key points, trends, and important information.""" - -# Output options -output = "text" # Options: text, json, markdown -# output_file = "" # Optional output file path -include_articles = false +``` +CLI flags > Environment variables > Config file > Defaults ``` -### Environment variables - -All configuration options can also be set via environment variables with the `LLM_AGGREGATOR_` prefix: - -| Variable | Description | -|----------|-------------| -| `LLM_AGGREGATOR_API_KEY` | LLM API key | -| `LLM_AGGREGATOR_BASE_URL` | API base URL (default: "https://api.deepseek.com") | -| `LLM_AGGREGATOR_MODEL` | Model name (default: "deepseek-chat") | -| `LLM_AGGREGATOR_MAX_TOKENS` | Maximum tokens in response (default: 4000) | -| `LLM_AGGREGATOR_TEMPERATURE` | Sampling temperature (default: 0.7) | -| `LLM_AGGREGATOR_TIMEOUT` | LLM request timeout in seconds (default: 300) | -| `LLM_AGGREGATOR_SYSTEM_PROMPT` | Custom system prompt | -| `LLM_AGGREGATOR_MAX_ARTICLES_PER_FEED` | Maximum articles per feed (default: 10) | -| `LLM_AGGREGATOR_MAX_DAYS_OLD` | Maximum article age in days (default: 7) | -| `LLM_AGGREGATOR_MAX_TOTAL_ARTICLES` | Maximum total articles (default: 20) | -| `LLM_AGGREGATOR_INCLUDE_KEYWORDS` | Comma‑separated include keywords | -| `LLM_AGGREGATOR_EXCLUDE_KEYWORDS` | Comma‑separated exclude keywords | -| `LLM_AGGREGATOR_OUTPUT` | Output format (default: "text") | -| `LLM_AGGREGATOR_OUTPUT_FILE` | Output file path | -| `LLM_AGGREGATOR_INCLUDE_ARTICLES` | Include articles in JSON output (true/false) | -| `LLM_AGGREGATOR_PLAIN` | Plain output without metadata (true/false) | -| `LLM_AGGREGATOR_STDIN` | Read RSS/Atom feed from stdin (true/false) | - -The API key can be provided via `--api‑key`, `LLM_AGGREGATOR_API_KEY` environment variable, or in the configuration file. - -## Example feeds file - -Create a file named `feeds.txt` with your favourite RSS feeds, one per line. -For example: - - https://news.ycombinator.com/rss - https://lwn.net/headlines/newrss - https://opensource.com/feed - https://www.phoronix.com/rss.php - -Then run: - - llm_aggregator -f feeds.txt -p "Summarise the top tech stories" - -## Dependencies - -`llm_aggregator` is written in Go and uses the following libraries: - -| Library | Description | -|---------|-------------| -| [`gofeed`](https://github.com/mmcdole/gofeed) | Robust RSS/Atom/JSON feed parser | -| [`openai‑go`](https://github.com/openai/openai-go) | Official OpenAI API library for Go | -| [`bubbletea`](https://github.com/charmbracelet/bubbletea) | TUI framework for terminal applications | -| [`lipgloss`](https://github.com/charmbracelet/lipgloss) | Library for styling terminal output | -| [`glamour`](https://github.com/charmbracelet/glamour) | Markdown rendering for terminal (used in TUI mode) | -| [`go‑arg`](https://github.com/alexflint/go-arg) | Struct‑based argument parsing | -| [`tiktoken-go`](https://github.com/pkoukk/tiktoken-go) | OpenAI's tiktoken BPE tokeniser | -| [`viper`](https://github.com/spf13/viper) | Configuration management | -| [`goquery`](https://github.com/PuerkitoBio/goquery) | jQuery‑like HTML scraping | +Create `~/.config/llm_aggregator/config.toml` (see +[docs/USAGE.md](docs/USAGE.md#configuration) for the full reference). -## How do I build `llm_aggregator`? +## Building -`llm_aggregator` can be built with a standard Go toolchain: - - go build ./cmd/llm_aggregator.go +Detailed build instructions (standard build, goreleaser, cross-compilation, +man page installation, tests, and linting) are in +[docs/BUILD.md](docs/BUILD.md). -For information about the project's test suite, see -[docs/TESTING.md](docs/TESTING.md). +--- ## Licence
A
docs/BUILD.md
@@ -0,0 +1,166 @@
+# Building from source + +## Prerequisites + +- Go 1.21 or later +- `git` (for fetching dependencies) + +## Standard build + +```bash +# Clone the repository +git clone https://codeberg.org/maxwelljensen/llm_aggregator.git +cd llm_aggregator + +# Download dependencies +go mod tidy + +# Build the binary +go build ./cmd/llm_aggregator.go + +# Run +./llm_aggregator --help +``` + +The binary is placed in the repository root as `llm_aggregator`. + +## Development build with version info + +During development builds, version information is injected via ldflags: + +```bash +go build -ldflags " + -s -w + -X main.version=$(git describe --tags --always --dirty 2>/dev/null || echo 'dev') + -X main.buildDate=$(date -u +%Y-%m-%d) +" ./cmd/llm_aggregator.go +``` + +The `--version` flag will then report the current git tag + commit hash. + +## Release builds (goreleaser) + +The project uses [goreleaser](https://goreleaser.com/) for cross-platform +release builds. The `.goreleaser.yaml` configuration builds for: + +| OS | Architectures | +|----|---------------| +| Linux | `386`, `amd64`, `arm64` | +| Windows | `386`, `amd64`, `arm64` | +| Darwin (macOS) | `amd64`, `arm64` | + +```bash +# Install goreleaser (once) +go install github.com/goreleaser/goreleaser/v2@latest + +# Build release binaries locally +goreleaser build --clean +``` + +Release artifacts are written to `dist/`. + +## Cross-compilation + +Go makes cross-compilation straightforward: + +```bash +# Linux on macOS (or vice versa) +GOOS=linux GOARCH=amd64 go build ./cmd/llm_aggregator.go + +# Windows +GOOS=windows GOARCH=amd64 go build -o llm_aggregator.exe ./cmd/llm_aggregator.go + +# ARM64 Linux +GOOS=linux GOARCH=arm64 go build ./cmd/llm_aggregator.go +``` + +## Running tests + +```bash +# All tests +go test ./... + +# With race detector +go test ./... -race + +# With coverage +go test ./... -cover +``` + +## Linting + +The project uses [golangci-lint](https://golangci-lint.run/): + +```bash +# Run linter +golangci-lint run + +# Auto-fix formatting issues +gofmt -w . +``` + +## Man pages + +`man` pages are in `docs/`: + +- `docs/llm_aggregator.1` — command reference +- `docs/llm_aggregator.5` — configuration file reference + +To install them system-wide: + +```bash +# Copy man pages to your man directory +cp docs/llm_aggregator.1 /usr/local/share/man/man1/ +cp docs/llm_aggregator.5 /usr/local/share/man/man5/ + +# Rebuild the whatis database (if needed) +mandb + +# View the man page +man llm_aggregator +``` + +To add them to your personal `$MANPATH`: + +```bash +# Add to shell profile (~/.bashrc, ~/.zshrc, etc.) +export MANPATH="$MANPATH:$HOME/.local/share/man" + +# Create the directories and copy man pages +mkdir -p ~/.local/share/man/man1 +mkdir -p ~/.local/share/man/man5 +cp docs/llm_aggregator.1 ~/.local/share/man/man1/ +cp docs/llm_aggregator.5 ~/.local/share/man/man5/ + +# Now you can run: +man llm_aggregator # section 1 (commands) +man 5 llm_aggregator # section 5 (config file) +``` + +## Verifying the build + +After building, verify the binary works: + +```bash +# Show version and default config +./llm_aggregator --version + +# Run a dry-run (no API key needed) +./llm_aggregator -f feeds.txt -p "test" -D --verbose +``` + +Where `feeds.txt` is a real feeds file to test end-to-end. + +## Dependencies + +| Library | Purpose | +|---------|---------| +| [`gofeed`](https://github.com/mmcdole/gofeed) | RSS/Atom/JSON feed parsing | +| [`openai-go`](https://github.com/openai/openai-go) | OpenAI-compatible API calls | +| [`bubbletea`](https://github.com/charmbracelet/bubbletea) | TUI framework | +| [`lipgloss`](https://github.com/charmbracelet/lipgloss) | Terminal styling | +| [`glamour`](https://github.com/charmbracelet/glamour) | Markdown rendering (TUI) | +| [`go-arg`](https://github.com/alexflint/go-arg) | CLI argument parsing | +| [`tiktoken-go`](https://github.com/pkoukk/tiktoken-go) | BPE token counting | +| [`viper`](https://github.com/spf13/viper) | Configuration management | +| [`goquery`](https://github.com/PuerkitoBio/goquery) | HTML scraping for article content |
A
docs/USAGE.md
@@ -0,0 +1,252 @@
+# Usage guide + +## Installation + +```bash +# From source +go build ./cmd/llm_aggregator.go +``` + +Or grab the latest binary in the +[Releases](https://codeberg.org/maxwelljensen/llm_aggregator/releases) section. + +## Man pages + +`man`` pages ship with the project in `docs/`: + +| File | Section | Description | +|------|---------|-------------| +| `docs/llm_aggregator.1` | 1 | Full command reference | +| `docs/llm_aggregator.5` | 5 | Configuration file reference | + +Install to your `$MANPATH`, then run: + +```bash +man llm_aggregator # section 1: command options +man 5 llm_aggregator # section 5: config file format +``` + +## Basic usage + +```bash +llm_aggregator --feeds-file FEEDS-FILE --prompt PROMPT [OPTIONS]... +``` + +`llm_aggregator` reads a list of RSS feed URLs, fetches the articles, filters +them by date and keywords, and sends a summary request to the LLM. The +result is printed to the terminal in your chosen format (text, markdown, or +JSON). + +--- + +## Feed sources + +### Feeds file + +Create a plain text file with one RSS/Atom feed URL per line. Lines starting +with `#` are comments; empty lines are skipped. + +```bash +# Example feeds.txt +https://news.ycombinator.com/rss +https://lwn.net/headlines/newrss + +# Linux news +https://opensource.com/feed +https://www.phoronix.com/rss.php +``` + +### stdin + +Read a single RSS/Atom feed directly from stdin: + +```bash +curl -s https://example.com/feed.xml | llm_aggregator --stdin -p "Summarise" +``` + +### Combining both + +```bash +llm_aggregator -f feeds.txt --stdin -p "Summarise all" +``` + +--- + +## Command reference + +### Required + +| Flag | Description | +|------|-------------| +| `-f, --feeds-file FILE` | Path to file containing RSS feed URLs (one per line) | +| `-p, --prompt PROMPT` | User prompt for summarisation/analysis | + +### Feed & filtering options + +| Flag | Default | Description | +|------|---------|-------------| +| `-n, --max-articles-per-feed N` | `10` | Maximum articles to fetch from each feed | +| `-d, --max-days-old N` | `7` | Only include articles from the last N days (0 for all) | +| `--max-total-articles N` | `20` | Maximum total articles to process | +| `-i, --include-keywords LIST` | — | Comma-separated keywords to include (case-insensitive) | +| `-e, --exclude-keywords LIST` | — | Comma-separated keywords to exclude (case-insensitive) | + +### LLM API options + +| Flag | Default | Description | +|------|---------|-------------| +| `--api-key KEY` | `$LLM_AGGREGATOR_API_KEY` | API key | +| `-m, --model MODEL` | `deepseek-chat` | Model name | +| `--base-url URL` | `https://api.deepseek.com` | API base URL | +| `--max-tokens N` | `4000` | Maximum tokens in response | +| `--temperature VALUE` | `0.7` | Sampling temperature (0.0 to 1.0) | +| `--timeout N` | `300` | LLM request timeout in seconds | +| `--system-prompt TEXT` | — | Custom system prompt for LLM | + +### Output options + +| Flag | Default | Description | +|------|---------|-------------| +| `-o, --output FORMAT` | `text` | Output format: `text`, `markdown`, or `json` | +| `--output-file FILE` | — | Write output to FILE instead of STDOUT | +| `--include-articles` | `false` | Include original articles in JSON output | +| `-P, --plain` | `false` | Output only the raw LLM response without formatting or metadata | + +### Interface options + +| Flag | Description | +|------|-------------| +| `-t, --tui` | Enable TUI interface with progress bar | +| `-v, --verbose` | Enable verbose logging | +| `-D, --dry-run` | Validate config, show article statistics, and exit without making LLM API calls | + +### Other + +| Flag | Description | +|------|-------------| +| `-h, --help` | Show help message and exit | +| `--version` | Show version information and exit | + +--- + +## Examples + +### Basic summarisation + +```bash +llm_aggregator -f feeds.txt -p "What are the latest AI-related trends in free software?" +``` + +### Custom model and higher token limit + +```bash +llm_aggregator -f feeds.txt -p "Code analysis" \ + -m deepseek-reasoner --max-tokens 8000 +``` + +### Local Ollama instance + +```bash +llm_aggregator -f feeds.txt -p "Summarise news" \ + --base-url "http://localhost:11434/v1" -m llama3 +``` + +### Keyword filtering + +Include only articles about Linux or open source: + +```bash +llm_aggregator -f feeds.txt -p "Linux news" \ + -i linux,opensource -d 3 +``` + +Exclude articles about Windows: + +```bash +llm_aggregator -f feeds.txt -p "Tech news" \ + -e windows,microsoft +``` + +### JSON output with articles + +```bash +llm_aggregator -f feeds.txt -p "Analyse AI developments" \ + -o json --output-file analysis.json --include-articles +``` + +### TUI mode + +```bash +llm_aggregator -f feeds.txt -p "Summarise tech news" -t +``` + +--- + +## Configuration + +`llm_aggregator` supports multiple configuration sources with the following +precedence order (highest to lowest): + +1. **Command-line arguments** — Override everything +2. **Environment variables** — Start with `LLM_AGGREGATOR_` prefix +3. **Configuration file** — `~/.config/llm_aggregator/config.toml` +4. **Built-in defaults** + +### Configuration file + +Create a TOML file at `~/.config/llm_aggregator/config.toml`: + +```toml +# Feed aggregation options +max_articles_per_feed = 10 +max_days_old = 7 +max_total_articles = 20 + +# Content filtering (comma-separated keywords) +# include_keywords = "linux,opensource" +# exclude_keywords = "windows,microsoft" + +# LLM API options +# api_key = "your_api_key_here" # Can also be set via LLM_AGGREGATOR_API_KEY env var +# base_url = "https://api.deepseek.com" # Optional custom API endpoint +model = "deepseek-chat" +max_tokens = 4000 +temperature = 0.7 + +# System prompt for LLM API +system_prompt = """You are an expert analyst and summariser. +You analyse content from multiple sources and provide +concise, insightful summaries based on user requests. +Focus on key points, trends, and important information.""" + +# Output options +output = "text" # Options: text, json, markdown +# output_file = "" # Optional output file path +include_articles = false +``` + +### Environment variables + +| Variable | Default | Description | +|----------|---------|-------------| +| `LLM_AGGREGATOR_API_KEY` | — | LLM API key | +| `LLM_AGGREGATOR_BASE_URL` | `https://api.deepseek.com` | API base URL | +| `LLM_AGGREGATOR_MODEL` | `deepseek-chat` | Model name | +| `LLM_AGGREGATOR_MAX_TOKENS` | `4000` | Maximum tokens in response | +| `LLM_AGGREGATOR_TEMPERATURE` | `0.7` | Sampling temperature | +| `LLM_AGGREGATOR_TIMEOUT` | `300` | LLM request timeout in seconds | +| `LLM_AGGREGATOR_SYSTEM_PROMPT` | — | Custom system prompt | +| `LLM_AGGREGATOR_MAX_ARTICLES_PER_FEED` | `10` | Maximum articles per feed | +| `LLM_AGGREGATOR_MAX_DAYS_OLD` | `7` | Maximum article age in days | +| `LLM_AGGREGATOR_MAX_TOTAL_ARTICLES` | `20` | Maximum total articles | +| `LLM_AGGREGATOR_INCLUDE_KEYWORDS` | — | Comma-separated include keywords | +| `LLM_AGGREGATOR_EXCLUDE_KEYWORDS` | — | Comma-separated exclude keywords | +| `LLM_AGGREGATOR_OUTPUT` | `text` | Output format | +| `LLM_AGGREGATOR_OUTPUT_FILE` | — | Output file path | +| `LLM_AGGREGATOR_INCLUDE_ARTICLES` | `false` | Include articles in JSON output | +| `LLM_AGGREGATOR_PLAIN` | `false` | Plain output without metadata | +| `LLM_AGGREGATOR_STDIN` | `false` | Read RSS/Atom feed from stdin | +| `LLM_AGGREGATOR_FEEDS_FILE` | — | Feeds file path | +| `LLM_AGGREGATOR_TUI` | `false` | Enable TUI mode | +| `LLM_AGGREGATOR_DRY_RUN` | `false` | Dry-run mode | +| `LLM_AGGREGATOR_VERBOSE` | `false` | Verbose logging |