root.go

  1/*
  2Copyright © 2025 NAME HERE <EMAIL ADDRESS>
  3*/
  4package cmd
  5
  6import (
  7	"context"
  8	"os"
  9	"sync"
 10
 11	tea "github.com/charmbracelet/bubbletea"
 12	"github.com/kujtimiihoxha/termai/internal/app"
 13	"github.com/kujtimiihoxha/termai/internal/db"
 14	"github.com/kujtimiihoxha/termai/internal/tui"
 15	"github.com/spf13/cobra"
 16	"github.com/spf13/viper"
 17)
 18
 19var rootCmd = &cobra.Command{
 20	Use:   "termai",
 21	Short: "A terminal ai assistant",
 22	Long:  `A terminal ai assistant`,
 23	RunE: func(cmd *cobra.Command, args []string) error {
 24		if cmd.Flag("help").Changed {
 25			cmd.Help()
 26			return nil
 27		}
 28		debug, _ := cmd.Flags().GetBool("debug")
 29		viper.Set("debug", debug)
 30		if debug {
 31			viper.Set("log.level", "debug")
 32		}
 33
 34		conn, err := db.Connect()
 35		if err != nil {
 36			return err
 37		}
 38		ctx := context.Background()
 39
 40		app := app.New(ctx, conn)
 41		app.Logger.Info("Starting termai...")
 42		tui := tea.NewProgram(
 43			tui.New(app),
 44			tea.WithAltScreen(),
 45		)
 46		app.Logger.Info("Setting up subscriptions...")
 47		ch, unsub := setupSubscriptions(app)
 48		defer unsub()
 49
 50		go func() {
 51			for msg := range ch {
 52				tui.Send(msg)
 53			}
 54		}()
 55		if _, err := tui.Run(); err != nil {
 56			return err
 57		}
 58		return nil
 59	},
 60}
 61
 62func setupSubscriptions(app *app.App) (chan tea.Msg, func()) {
 63	ch := make(chan tea.Msg)
 64	wg := sync.WaitGroup{}
 65	ctx, cancel := context.WithCancel(app.Context)
 66
 67	if viper.GetBool("debug") {
 68		sub := app.Logger.Subscribe(ctx)
 69		wg.Add(1)
 70		go func() {
 71			for ev := range sub {
 72				ch <- ev
 73			}
 74			wg.Done()
 75		}()
 76	}
 77	{
 78		sub := app.Sessions.Subscribe(ctx)
 79		wg.Add(1)
 80		go func() {
 81			for ev := range sub {
 82				ch <- ev
 83			}
 84			wg.Done()
 85		}()
 86	}
 87	return ch, func() {
 88		cancel()
 89		wg.Wait()
 90		close(ch)
 91	}
 92}
 93
 94// Execute adds all child commands to the root command and sets flags appropriately.
 95// This is called by main.main(). It only needs to happen once to the rootCmd.
 96func Execute() {
 97	err := rootCmd.Execute()
 98	if err != nil {
 99		os.Exit(1)
100	}
101}
102
103func loadConfig() {
104	viper.SetConfigName(".termai")
105	viper.SetConfigType("yaml")
106	viper.AddConfigPath("$HOME")
107	viper.AddConfigPath("$XDG_CONFIG_HOME/termai")
108	viper.AddConfigPath(".")
109	viper.SetEnvPrefix("TERMAI")
110	// SET DEFAULTS
111	viper.SetDefault("log.level", "info")
112	viper.SetDefault("data.dir", ".termai")
113
114	//
115	viper.ReadInConfig()
116}
117
118func init() {
119	loadConfig()
120	// Here you will define your flags and configuration settings.
121	// Cobra supports persistent flags, which, if defined here,
122	// will be global for your application.
123
124	rootCmd.Flags().BoolP("help", "h", false, "Help")
125	rootCmd.Flags().BoolP("debug", "d", false, "Help")
126}