all repos — llm_aggregator @ 13547fc860356e4f64bfa097f7681fdd90f33df7

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

cmd/llm_aggregator.go (view raw)

  1package main
  2
  3import (
  4	"context"
  5	"fmt"
  6	"os"
  7	"os/signal"
  8	"syscall"
  9
 10	"llm_aggregator/internal/cli"
 11	"llm_aggregator/internal/runtime"
 12	"llm_aggregator/internal/tui"
 13
 14	tea "github.com/charmbracelet/bubbletea"
 15)
 16
 17var (
 18	version   string
 19	buildDate string
 20)
 21
 22func main() {
 23	cli.BuildDate = buildDate
 24	cli.Version = version
 25	// Parse command line arguments
 26	args, err := cli.ParseArgs()
 27	if err != nil {
 28		fmt.Fprintf(os.Stderr, "Error parsing arguments: %v\n", err)
 29		os.Exit(1)
 30	}
 31
 32	// Create runtime configuration
 33	rt := runtime.NewRuntime()
 34	rt.FeedsFile = args.FeedsFile
 35	rt.MaxArticlesPerFeed = args.MaxArticlesPerFeed
 36	rt.MaxDaysOld = args.MaxDaysOld
 37	rt.MaxTotalArticles = args.MaxTotalArticles
 38	rt.IncludeKeywords = cli.ParseKeywords(args.IncludeKeywords)
 39	rt.ExcludeKeywords = cli.ParseKeywords(args.ExcludeKeywords)
 40	rt.APIKey = args.APIKey
 41	if rt.APIKey == "" {
 42		rt.APIKey = os.Getenv("DEEPSEEK_API_KEY")
 43	}
 44	rt.Model = args.Model
 45	rt.MaxTokens = args.MaxTokens
 46	rt.Temperature = args.Temperature
 47	rt.Prompt = args.Prompt
 48	rt.SystemPrompt = args.SystemPrompt
 49	rt.Output = args.Output
 50	rt.OutputFile = args.OutputFile
 51	rt.IncludeArticles = args.IncludeArticles
 52	rt.Verbose = args.Verbose
 53
 54	// Validate API key
 55	if rt.APIKey == "" {
 56		fmt.Fprintln(os.Stderr, "Error: DeepSeek API key is required. Set via --api-key or DEEPSEEK_API_KEY environment variable.")
 57		os.Exit(1)
 58	}
 59
 60	// Run with TUI if requested
 61	if args.TUI {
 62		runWithTUI(rt)
 63	} else {
 64		runWithoutTUI(rt, args.Verbose)
 65	}
 66}
 67
 68func runWithTUI(rt *runtime.Runtime) {
 69	// Create TUI model
 70	m := tui.New()
 71	p := tea.NewProgram(m, tea.WithAltScreen())
 72
 73	// Run TUI in goroutine
 74	done := make(chan error)
 75	go func() {
 76		if _, err := p.Run(); err != nil {
 77			done <- err
 78			return
 79		}
 80		done <- nil
 81	}()
 82
 83	// Run aggregation in another goroutine
 84	go func() {
 85		// Create context for cancellation
 86		ctx, cancel := context.WithCancel(context.Background())
 87		defer cancel()
 88
 89		// Set up signal handling
 90		sigCh := make(chan os.Signal, 1)
 91		signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
 92
 93		// Handle signals
 94		go func() {
 95			<-sigCh
 96			p.Send(tui.Error("Operation cancelled"))
 97			cancel()
 98		}()
 99
100		// Update TUI status
101		p.Send(tui.Step(1, fmt.Sprintf("Aggregating feeds from: %s", rt.FeedsFile)))
102
103		// Execute the runtime
104		err := rt.Execute(ctx)
105		if err != nil {
106			p.Send(tui.Error(err.Error()))
107			return
108		}
109
110		// Update TUI status
111		p.Send(tui.StepWithCounts(5, "Formatting output", len(rt.Articles), len(rt.Articles)))
112
113		// Write output
114		if rt.OutputFile != "" {
115			if err := rt.WriteOutputToFile(); err != nil {
116				p.Send(tui.Error(fmt.Sprintf("Error writing output: %v", err)))
117				return
118			}
119			p.Send(tui.Step(6, fmt.Sprintf("Output written to: %s", rt.OutputFile)))
120		} else {
121			if err := rt.WriteOutput(os.Stdout); err != nil {
122				p.Send(tui.Error(fmt.Sprintf("Error writing output: %v", err)))
123				return
124			}
125			p.Send(tui.Step(6, "Output complete"))
126		}
127
128		// Mark as done
129		p.Send(tui.Step(7, "Processing completed successfully"))
130	}()
131
132	// Wait for TUI to complete
133	if err := <-done; err != nil {
134		fmt.Fprintf(os.Stderr, "TUI error: %v\n", err)
135		os.Exit(1)
136	}
137}
138
139func runWithoutTUI(rt *runtime.Runtime, verbose bool) {
140	// Set verbose flag on runtime
141	rt.Verbose = verbose
142
143	// Execute the runtime
144	err := rt.Execute(context.Background())
145	if err != nil {
146		fmt.Fprintf(os.Stderr, "Error: %v\n", err)
147		os.Exit(1)
148	}
149
150	// Write output
151	if rt.OutputFile != "" {
152		if err := rt.WriteOutputToFile(); err != nil {
153			fmt.Fprintf(os.Stderr, "Error writing output: %v\n", err)
154			os.Exit(1)
155		}
156	} else {
157		if err := rt.WriteOutput(os.Stdout); err != nil {
158			fmt.Fprintf(os.Stderr, "Error writing output: %v\n", err)
159			os.Exit(1)
160		}
161	}
162}
163