all repos — llm_aggregator @ 51cde19dd83544f497e4ce82d27d3524433979b3

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

internal/cli/args.go (view raw)

  1package cli
  2
  3import (
  4	"fmt"
  5	"os"
  6	"strings"
  7
  8	"github.com/alexflint/go-arg"
  9)
 10
 11var (
 12	Version   string
 13	BuildDate string
 14)
 15
 16// Args represents command-line arguments.
 17type Args struct {
 18	FeedsFile string `arg:"--feeds-file,required" help:"Path to file containing RSS feed URLs (one per line)"`
 19	Prompt    string `arg:"--prompt,required" help:"User prompt for summarisation/analysis"`
 20
 21	// Feed aggregation options
 22	MaxArticlesPerFeed *int `arg:"--max-articles-per-feed" help:"Maximum articles to fetch from each feed"`
 23	MaxDaysOld         *int `arg:"--max-days-old" help:"Only include articles from the last N days"`
 24	MaxTotalArticles   *int `arg:"--max-total-articles" help:"Maximum total articles to process"`
 25
 26	// Content filtering
 27	IncludeKeywords string `arg:"--include-keywords" help:"Comma-separated list of keywords to include (case-insensitive)"`
 28	ExcludeKeywords string `arg:"--exclude-keywords" help:"Comma-separated list of keywords to exclude (case-insensitive)"`
 29
 30	// LLM API options
 31	APIKey      *string  `arg:"--api-key" help:"OpenAI-compatible API key (default: read from LLM_AGGREGATOR_API_KEY env var)"`
 32	BaseURL     *string  `arg:"--base-url" help:"API base URL"`
 33	Model       *string  `arg:"--model" help:"LLM model to use"`
 34	MaxTokens   *int     `arg:"--max-tokens" help:"Maximum tokens in response"`
 35	Temperature *float64 `arg:"--temperature" help:"Sampling temperature (0.0 to 1.0)"`
 36
 37	// Output options
 38	Output          string `arg:"--output" help:"Output format" choice:"text,json,markdown"`
 39	OutputFile      string `arg:"--output-file" help:"Write output to file (default: stdout)"`
 40	IncludeArticles bool   `arg:"--include-articles" help:"Include original articles in JSON output"`
 41
 42	// System options
 43	SystemPrompt string `arg:"--system-prompt" help:"Custom system prompt for LLM"`
 44	TUI          bool   `arg:"--tui" help:"Enable TUI interface with progress bar"`
 45	Verbose      bool   `arg:"-v,--verbose" help:"Show verbose output"`
 46	ShowVersion  bool   `arg:"--version" help:"Show version"`
 47	DryRun       bool   `arg:"--dry-run" help:"Validate config, show article statistics, and exit without making LLM API calls"`
 48}
 49
 50// Version returns the version string.
 51func (Args) Version() string {
 52	return fmt.Sprintf("llm_aggregator v%s (built %s)", Version, BuildDate)
 53}
 54
 55// Description returns the program description.
 56func (Args) Description() string {
 57	return `LLM Aggregator - Aggregate RSS feeds and summarise with LLM API
 58
 59Examples:
 60  # Basic usage with prompts
 61  llm_aggregator --feeds-file feeds.txt --prompt "What are the latest trends in free software?"
 62  
 63  # Validate configuration and preview articles (no LLM API calls)
 64  llm_aggregator --feeds-file feeds.txt --prompt "Summarise news" --dry-run
 65  
 66  # With custom LLM model
 67  llm_aggregator --feeds-file feeds.txt --prompt "Summarise tech news" --model deepseek-coder
 68  
 69  # Output to JSON file
 70  llm_aggregator --feeds-file feeds.txt --prompt "Analyse AI developments" --output json --output-file analysis.json
 71  
 72  # Filter by keywords
 73  llm_aggregator --feeds-file feeds.txt --prompt "Linux news" --include-keywords linux,opensource
 74
 75Environment Variables:
 76  LLM_AGGREGATOR_API_KEY: Your LLM API key (required if not provided via --api-key)`
 77}
 78
 79// ParseKeywords parses comma-separated keywords string into list.
 80func ParseKeywords(keywordString string) []string {
 81	if keywordString == "" {
 82		return nil
 83	}
 84	keywords := strings.Split(keywordString, ",")
 85	result := make([]string, 0, len(keywords))
 86	for _, kw := range keywords {
 87		if trimmed := strings.TrimSpace(kw); trimmed != "" {
 88			result = append(result, trimmed)
 89		}
 90	}
 91	return result
 92}
 93
 94// ParseArgs parses command line arguments.
 95func ParseArgs() (*Args, error) {
 96	var args Args
 97	parser, err := arg.NewParser(arg.Config{
 98		Program: "llm_aggregator",
 99	}, &args)
100	if err != nil {
101		return nil, err
102	}
103
104	// Handle help and version flags before checking required fields
105	if len(os.Args) > 1 {
106		if os.Args[1] == "-h" || os.Args[1] == "--help" {
107			parser.WriteHelp(os.Stdout)
108			os.Exit(0)
109		}
110		if os.Args[1] == "--version" {
111			fmt.Printf("llm_aggregator v%s (built %s)", Version, BuildDate)
112			os.Exit(0)
113		}
114	}
115
116	err = parser.Parse(os.Args[1:])
117	if err != nil {
118		return nil, err
119	}
120
121	return &args, nil
122}
123
124// ToViperMap converts Args to a map for binding to Viper.
125// Only non-nil values (explicitly provided CLI flags) are included.
126func (a *Args) ToViperMap() map[string]any {
127	m := map[string]any{}
128	if a.FeedsFile != "" {
129		m["feeds_file"] = a.FeedsFile
130	}
131	if a.MaxArticlesPerFeed != nil {
132		m["max_articles_per_feed"] = *a.MaxArticlesPerFeed
133	}
134	if a.MaxDaysOld != nil {
135		m["max_days_old"] = *a.MaxDaysOld
136	}
137	if a.MaxTotalArticles != nil {
138		m["max_total_articles"] = *a.MaxTotalArticles
139	}
140	if a.IncludeKeywords != "" {
141		m["include_keywords"] = a.IncludeKeywords
142	}
143	if a.ExcludeKeywords != "" {
144		m["exclude_keywords"] = a.ExcludeKeywords
145	}
146	if a.APIKey != nil {
147		m["api_key"] = *a.APIKey
148	}
149	if a.BaseURL != nil {
150		m["base_url"] = *a.BaseURL
151	}
152	if a.Model != nil {
153		m["model"] = *a.Model
154	}
155	if a.MaxTokens != nil {
156		m["max_tokens"] = *a.MaxTokens
157	}
158	if a.Temperature != nil {
159		m["temperature"] = *a.Temperature
160	}
161	if a.Prompt != "" {
162		m["prompt"] = a.Prompt
163	}
164	if a.SystemPrompt != "" {
165		m["system_prompt"] = a.SystemPrompt
166	}
167	if a.Output != "" {
168		m["output"] = a.Output
169	}
170	if a.OutputFile != "" {
171		m["output_file"] = a.OutputFile
172	}
173	if a.IncludeArticles {
174		m["include_articles"] = a.IncludeArticles
175	}
176	return m
177}