all repos — llm_aggregator @ af6da634ea141bc028760fd78253d03cee78a82f

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

feat: add --plain flag for raw LLM output without formatting

- Add `-P`/`--plain` CLI flag (`internal/cli/args.go`)
  - Flag suppresses all formatting and metadata, outputting only the LLM
    response
- Implement plain output logic in `internal/runtime/runtime.go`
  - Skip formatter when plain mode is active, write summary directly
- Wire `--plain` through configuration layer
  (`internal/config/config.go`)
  - Map flag to runtime config, bind to `LLM_AGGREGATOR_PLAIN` env var
- Update man page (`docs/llm_aggregator.1`) with new option and
  environment variable
- Add `--plain` to styled help and usage examples
  (`internal/cli/help.go`)
- Extend tests for args-to-viper mapping and config parsing to cover
  plain flag
- Bump version to 0.12.0 and modify `CHANGELOG.md` accordingly
- Bump man page version to 0.12.0 (drive-by)
Maxwell Jensen 85795372+maxwelljens@users.noreply.github.com
Sun, 26 Apr 2026 20:49:14 +0200
commit

af6da634ea141bc028760fd78253d03cee78a82f

parent

e6f508220e5dbe8a9333b4ad603b5add4b13af7b

M CHANGELOG.mdCHANGELOG.md

@@ -6,6 +6,13 @@ The format is based on [Keep a

Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.12.0] - 2026-04-26 + +### Added + +- **`-P/--plain` flag**: Output the raw LLM summary without any formatting or + metadata. Useful when the summary is to be consumed by downstream scripts. + ## [0.11.0] - 2026-04-23 - Progress bar now tracks estimated token count (via tiktoken) before sending
M README.mdREADME.md

@@ -36,6 +36,7 @@ --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

@@ -189,6 +190,7 @@ | `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) | The API key can be provided via `--api‑key`, `LLM_AGGREGATOR_API_KEY` environment variable, or in the configuration file.
M docs/llm_aggregator.1docs/llm_aggregator.1

@@ -1,5 +1,5 @@

.\" Man page for llm_aggregator -.TH LLM_AGGREGATOR 1 "23 April 2026" "0.9.0" "User Commands" +.TH LLM_AGGREGATOR 1 "26 April 2026" "0.12.0" "User Commands" .SH NAME llm_aggregator \- aggregate RSS feeds and summarise them with LLMs .SH SYNOPSIS

@@ -114,6 +114,13 @@ .B \-\-system\-prompt \fITEXT\fR

Custom system prompt for the LLM. If not specified, a default prompt is used. .SS Output Options .TP +.B \-P\fR,\fB \-\-plain +Output only the raw LLM response without any formatting or metadata. When +active, +.B -o\fR/ +.B \-\-output +has no effect. Useful for piping or post-processing. +.TP .B \-o\fR,\fB -o \fIFORMAT\fR Output format. Must be one of: .RS 12

@@ -135,6 +142,13 @@ .B \-\-include\-articles

Include the original articles in JSON output. Only applicable when .B -o json is used. +.TP +.B \-P\fR,\fB \-\-plain +Output only the raw LLM response without any formatting or metadata. When +active, +.B -o\fR/ +.B \-\-output +has no effect. Useful for piping or post-processing. .SS Interface Options .TP .B \-D\fR,\fB \-\-dry\-run

@@ -215,6 +229,9 @@ Output file path

.TP .B LLM_AGGREGATOR_INCLUDE_ARTICLES Include articles in JSON output (true/false) +.TP +.B LLM_AGGREGATOR_PLAIN +Plain output without metadata (true/false) .SH CONFIGURATION FILE Configuration can also be provided via a TOML file at: .PP
M internal/cli/args.gointernal/cli/args.go

@@ -38,6 +38,7 @@ // Output options

Output string `arg:"-o,--output" help:"Output format" choice:"text,json,markdown"` OutputFile string `arg:"--output-file" help:"Write output to file (default: stdout)"` IncludeArticles bool `arg:"--include-articles" help:"Include original articles in JSON output"` + Plain bool `arg:"-P,--plain" help:"Output only the raw LLM response without any formatting or metadata"` // System options SystemPrompt string `arg:"--system-prompt" help:"Custom system prompt for LLM"`

@@ -158,6 +159,9 @@ m["output_file"] = a.OutputFile

} if a.IncludeArticles { m["include_articles"] = a.IncludeArticles + } + if a.Plain { + m["plain"] = a.Plain } return m }
M internal/cli/args_test.gointernal/cli/args_test.go

@@ -450,6 +450,7 @@ SystemPrompt: "Custom system prompt",

Output: "json", OutputFile: "/tmp/output.json", IncludeArticles: true, + Plain: true, } viperMap := args.ToViperMap()

@@ -471,6 +472,7 @@ {"system_prompt", "Custom system prompt"},

{"output", "json"}, {"output_file", "/tmp/output.json"}, {"include_articles", true}, + {"plain", true}, } for _, tt := range tests {
M internal/cli/help.gointernal/cli/help.go

@@ -223,6 +223,11 @@ {

Name: "--include-articles", Description: "Include original articles in JSON output", }, + { + Short: "-P", + Name: "--plain", + Description: "Output only the raw LLM response without any formatting or metadata", + }, }, }, {

@@ -280,6 +285,7 @@ {"llm_aggregator -f feeds.txt -p \"Linux news\" -i linux,opensource -d 3", "Filter by keywords and age"},

{"llm_aggregator -f feeds.txt -p \"Summarise tech news\" -t", "With TUI progress bar"}, {"llm_aggregator -f feeds.txt -p \"Summarise news\" -D", "Dry run (no LLM API calls)"}, {"llm_aggregator -f feeds.txt -p \"Analyse AI\" -o json --output-file output.json", "JSON output to file"}, + {"llm_aggregator -f feeds.txt -p \"Summarise news\" -P", "Plain output (no metadata)"}, } for _, ex := range examples {
M internal/config/config.gointernal/config/config.go

@@ -37,6 +37,7 @@ // Output options

Output string `mapstructure:"output"` OutputFile string `mapstructure:"output_file"` IncludeArticles bool `mapstructure:"include_articles"` + Plain bool `mapstructure:"plain"` } // GetViper returns the global viper instance with all configuration sources set up.

@@ -197,6 +198,7 @@ // Output options

rt.Output = v.GetString("output") rt.OutputFile = v.GetString("output_file") rt.IncludeArticles = v.GetBool("include_articles") + rt.Plain = v.GetBool("plain") return rt }

@@ -220,6 +222,7 @@ // Output options

v.BindEnv("output", "LLM_AGGREGATOR_OUTPUT") v.BindEnv("output_file", "LLM_AGGREGATOR_OUTPUT_FILE") v.BindEnv("include_articles", "LLM_AGGREGATOR_INCLUDE_ARTICLES") + v.BindEnv("plain", "LLM_AGGREGATOR_PLAIN") } // GetConfigPath returns the path to the config file.
M internal/config/config_test.gointernal/config/config_test.go

@@ -383,6 +383,7 @@ "--exclude-keywords": "advertisement",

"--max-tokens": "1000", "--temperature": "0.3", "--output": "json", + "--plain": "", }, expectFeeds: feedsFile, expectModel: "deepseek-coder",

@@ -399,7 +400,10 @@ key := k

if strings.HasPrefix(key, "--") { key = key[2:] } - args = append(args, "--"+key, v) + args = append(args, "--"+key) + if v != "" { + args = append(args, v) + } } // Save original os.Args

@@ -433,6 +437,11 @@ if apiKey, ok := tt.cliArgs["--api-key"]; ok {

if parsedArgs.APIKey != nil && *parsedArgs.APIKey != apiKey { t.Errorf("APIKey = %q, want %q", *parsedArgs.APIKey, apiKey) } + } + + // Verify plain flag + if tt.name == "all CLI options" && !parsedArgs.Plain { + t.Error("Plain should be true when --plain is provided") } }) }
M internal/runtime/runtime.gointernal/runtime/runtime.go

@@ -34,6 +34,7 @@ SystemPrompt string

Output string OutputFile string IncludeArticles bool + Plain bool Verbose bool // State

@@ -135,6 +136,13 @@ }

// WriteOutput writes the formatted output to the specified writer func (r *Runtime) WriteOutput(writer io.Writer) error { + if r.Plain { + if _, err := fmt.Fprint(writer, r.Summary); err != nil { + return fmt.Errorf("error writing plain output: %w", err) + } + return nil + } + formatter, err := output.NewFormatter(r.Output) if err != nil { return fmt.Errorf("error creating formatter: %w", err)