root.go 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. package cmd
  2. import (
  3. "context"
  4. "os"
  5. "sync"
  6. tea "github.com/charmbracelet/bubbletea"
  7. "github.com/kujtimiihoxha/termai/internal/app"
  8. "github.com/kujtimiihoxha/termai/internal/db"
  9. "github.com/kujtimiihoxha/termai/internal/llm/models"
  10. "github.com/kujtimiihoxha/termai/internal/tui"
  11. zone "github.com/lrstanley/bubblezone"
  12. "github.com/spf13/cobra"
  13. "github.com/spf13/viper"
  14. )
  15. var rootCmd = &cobra.Command{
  16. Use: "termai",
  17. Short: "A terminal ai assistant",
  18. Long: `A terminal ai assistant`,
  19. RunE: func(cmd *cobra.Command, args []string) error {
  20. if cmd.Flag("help").Changed {
  21. cmd.Help()
  22. return nil
  23. }
  24. debug, _ := cmd.Flags().GetBool("debug")
  25. viper.Set("debug", debug)
  26. if debug {
  27. viper.Set("log.level", "debug")
  28. }
  29. conn, err := db.Connect()
  30. if err != nil {
  31. return err
  32. }
  33. ctx := context.Background()
  34. app := app.New(ctx, conn)
  35. app.Logger.Info("Starting termai...")
  36. zone.NewGlobal()
  37. tui := tea.NewProgram(
  38. tui.New(app),
  39. tea.WithAltScreen(),
  40. tea.WithMouseCellMotion(),
  41. )
  42. app.Logger.Info("Setting up subscriptions...")
  43. ch, unsub := setupSubscriptions(app)
  44. defer unsub()
  45. go func() {
  46. for msg := range ch {
  47. tui.Send(msg)
  48. }
  49. }()
  50. if _, err := tui.Run(); err != nil {
  51. return err
  52. }
  53. return nil
  54. },
  55. }
  56. func setupSubscriptions(app *app.App) (chan tea.Msg, func()) {
  57. ch := make(chan tea.Msg)
  58. wg := sync.WaitGroup{}
  59. ctx, cancel := context.WithCancel(app.Context)
  60. {
  61. sub := app.Logger.Subscribe(ctx)
  62. wg.Add(1)
  63. go func() {
  64. for ev := range sub {
  65. ch <- ev
  66. }
  67. wg.Done()
  68. }()
  69. }
  70. {
  71. sub := app.Sessions.Subscribe(ctx)
  72. wg.Add(1)
  73. go func() {
  74. for ev := range sub {
  75. ch <- ev
  76. }
  77. wg.Done()
  78. }()
  79. }
  80. {
  81. sub := app.Messages.Subscribe(ctx)
  82. wg.Add(1)
  83. go func() {
  84. for ev := range sub {
  85. ch <- ev
  86. }
  87. wg.Done()
  88. }()
  89. }
  90. {
  91. sub := app.LLM.Subscribe(ctx)
  92. wg.Add(1)
  93. go func() {
  94. for ev := range sub {
  95. ch <- ev
  96. }
  97. wg.Done()
  98. }()
  99. }
  100. {
  101. sub := app.Permissions.Subscribe(ctx)
  102. wg.Add(1)
  103. go func() {
  104. for ev := range sub {
  105. ch <- ev
  106. }
  107. wg.Done()
  108. }()
  109. }
  110. return ch, func() {
  111. cancel()
  112. wg.Wait()
  113. close(ch)
  114. }
  115. }
  116. func Execute() {
  117. err := rootCmd.Execute()
  118. if err != nil {
  119. os.Exit(1)
  120. }
  121. }
  122. func loadConfig() {
  123. viper.SetConfigName(".termai")
  124. viper.SetConfigType("yaml")
  125. viper.AddConfigPath("$HOME")
  126. viper.AddConfigPath("$XDG_CONFIG_HOME/termai")
  127. viper.AddConfigPath(".")
  128. viper.SetEnvPrefix("TERMAI")
  129. // SET DEFAULTS
  130. viper.SetDefault("log.level", "info")
  131. viper.SetDefault("data.dir", ".termai")
  132. // LLM
  133. viper.SetDefault("models.big", string(models.DefaultBigModel))
  134. viper.SetDefault("models.small", string(models.DefaultLittleModel))
  135. viper.SetDefault("providers.openai.key", os.Getenv("OPENAI_API_KEY"))
  136. viper.SetDefault("providers.anthropic.key", os.Getenv("ANTHROPIC_API_KEY"))
  137. viper.SetDefault("providers.groq.key", os.Getenv("GROQ_API_KEY"))
  138. viper.SetDefault("providers.common.max_tokens", 4000)
  139. viper.SetDefault("agents.default", "coder")
  140. viper.ReadInConfig()
  141. workdir, err := os.Getwd()
  142. if err != nil {
  143. panic(err)
  144. }
  145. viper.Set("wd", workdir)
  146. }
  147. func init() {
  148. loadConfig()
  149. rootCmd.Flags().BoolP("help", "h", false, "Help")
  150. rootCmd.Flags().BoolP("debug", "d", false, "Help")
  151. }