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" default:"10"`
23 MaxDaysOld int `arg:"--max-days-old" help:"Only include articles from the last N days (0 for all)" default:"7"`
24 MaxTotalArticles int `arg:"--max-total-articles" help:"Maximum total articles to process" default:"20"`
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" default:"https://api.deepseek.com"`
33 Model string `arg:"--model" help:"LLM model to use" default:"deepseek-chat"`
34 MaxTokens int `arg:"--max-tokens" help:"Maximum tokens in response" default:"4000"`
35 Temperature float64 `arg:"--temperature" help:"Sampling temperature (0.0 to 1.0)" default:"0.7"`
36
37 // Output options
38 Output string `arg:"--output" help:"Output format" default:"text" 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}
48
49// Version returns the version string.
50func (Args) Version() string {
51 return fmt.Sprintf("llm_aggregator v%s (built %s)", Version, BuildDate)
52}
53
54// Description returns the program description.
55func (Args) Description() string {
56 return `LLM Aggregator - Aggregate RSS feeds and summarise with LLM API
57
58Examples:
59 # Basic usage with prompts
60 llm_aggregator --feeds-file feeds.txt --prompt "What are the latest trends in free software?"
61
62 # With custom LLM model
63 llm_aggregator --feeds-file feeds.txt --prompt "Summarise tech news" --model deepseek-coder
64
65 # Output to JSON file
66 llm_aggregator --feeds-file feeds.txt --prompt "Analyse AI developments" --output json --output-file analysis.json
67
68 # Filter by keywords
69 llm_aggregator --feeds-file feeds.txt --prompt "Linux news" --include-keywords linux,opensource
70
71Environment Variables:
72 LLM_AGGREGATOR_API_KEY: Your LLM API key (required if not provided via --api-key)`
73}
74
75// ParseKeywords parses comma-separated keywords string into list.
76func ParseKeywords(keywordString string) []string {
77 if keywordString == "" {
78 return nil
79 }
80 keywords := strings.Split(keywordString, ",")
81 result := make([]string, 0, len(keywords))
82 for _, kw := range keywords {
83 if trimmed := strings.TrimSpace(kw); trimmed != "" {
84 result = append(result, trimmed)
85 }
86 }
87 return result
88}
89
90// ParseArgs parses command line arguments.
91func ParseArgs() (*Args, error) {
92 var args Args
93 parser, err := arg.NewParser(arg.Config{
94 Program: "llm_aggregator",
95 }, &args)
96 if err != nil {
97 return nil, err
98 }
99
100 // Handle help and version flags before checking required fields
101 if len(os.Args) > 1 {
102 if os.Args[1] == "-h" || os.Args[1] == "--help" {
103 parser.WriteHelp(os.Stdout)
104 os.Exit(0)
105 }
106 if os.Args[1] == "--version" {
107 fmt.Printf("llm_aggregator v%s (built %s)", Version, BuildDate)
108 os.Exit(0)
109 }
110 }
111
112 err = parser.Parse(os.Args[1:])
113 if err != nil {
114 return nil, err
115 }
116
117 return &args, nil
118}
119
120// ToViperMap converts Args to a map for binding to Viper.
121// Only non-empty/non-zero values are included.
122func (a *Args) ToViperMap() map[string]any {
123 return map[string]any{
124 "max_articles_per_feed": a.MaxArticlesPerFeed,
125 "max_days_old": a.MaxDaysOld,
126 "max_total_articles": a.MaxTotalArticles,
127 "include_keywords": a.IncludeKeywords,
128 "exclude_keywords": a.ExcludeKeywords,
129 "api_key": a.APIKey,
130 "base_url": a.BaseURL,
131 "model": a.Model,
132 "max_tokens": a.MaxTokens,
133 "temperature": a.Temperature,
134 "system_prompt": a.SystemPrompt,
135 "output": a.Output,
136 "output_file": a.OutputFile,
137 "include_articles": a.IncludeArticles,
138 }
139}