adamdottv 9 месяцев назад
Родитель
Сommit
8cbfc581b5

+ 0 - 1
internal/config/init.go

@@ -58,4 +58,3 @@ func MarkProjectInitialized() error {
 
 	return nil
 }
-

+ 0 - 2
internal/llm/agent/agent-tool.go

@@ -88,8 +88,6 @@ func (b *agentTool) Run(ctx context.Context, call tools.ToolCall) (tools.ToolRes
 	}
 
 	parentSession.Cost += updatedSession.Cost
-	parentSession.PromptTokens += updatedSession.PromptTokens
-	parentSession.CompletionTokens += updatedSession.CompletionTokens
 
 	_, err = b.sessions.Update(ctx, parentSession)
 	if err != nil {

+ 13 - 51
internal/llm/agent/agent.go

@@ -300,53 +300,6 @@ func (a *agent) createUserMessage(ctx context.Context, sessionID, content string
 }
 
 func (a *agent) streamAndHandleEvents(ctx context.Context, sessionID string, msgHistory []message.Message) (message.Message, *message.Message, error) {
-	// Check if we need to auto-compact based on token count
-	contextWindow := a.provider.Model().ContextWindow
-	maxTokens := a.provider.MaxTokens()
-	threshold := int64(float64(contextWindow) * 0.80)
-	usage, err := a.GetUsage(ctx, sessionID)
-	if err != nil || usage == nil {
-		return message.Message{}, nil, fmt.Errorf("failed to get usage: %w", err)
-	}
-
-	// If we're approaching the context window limit, trigger auto-compaction
-	if false && (*usage+maxTokens) >= threshold {
-		status.Info(fmt.Sprintf("Auto-compaction triggered for session %s. Estimated tokens: %d, Threshold: %d", sessionID, usage, threshold))
-
-		// Perform compaction with pause/resume to ensure safety
-		if err := a.CompactSession(ctx, sessionID); err != nil {
-			status.Error(fmt.Sprintf("Auto-compaction failed: %v", err))
-			// Continue with the request even if compaction fails
-		} else {
-			// Re-fetch session details after compaction
-			currentSession, err := a.sessions.Get(ctx, sessionID)
-			if err != nil {
-				return message.Message{}, nil, fmt.Errorf("failed to get session after compaction: %w", err)
-			}
-
-			// Re-prepare messages using the new summary
-			var sessionMessages []message.Message
-			if currentSession.Summary != "" && currentSession.SummarizedAt > 0 {
-				// If summary exists, only fetch messages after the summarization timestamp
-				sessionMessages, err = a.messages.ListAfter(ctx, sessionID, currentSession.SummarizedAt)
-				if err != nil {
-					return message.Message{}, nil, fmt.Errorf("failed to list messages after compaction: %w", err)
-				}
-
-				// Create a new message history with the summary and messages after summarization
-				summaryMessage := message.Message{
-					Role: message.Assistant,
-					Parts: []message.ContentPart{
-						message.TextContent{Text: currentSession.Summary},
-					},
-				}
-
-				// Replace msgHistory with the new compacted version
-				msgHistory = append([]message.Message{summaryMessage}, sessionMessages...)
-			}
-		}
-	}
-
 	eventChan := a.provider.StreamResponse(ctx, msgHistory, a.tools)
 
 	assistantMsg, err := a.messages.Create(ctx, sessionID, message.CreateMessageParams{
@@ -541,8 +494,8 @@ func (a *agent) TrackUsage(ctx context.Context, sessionID string, model models.M
 		model.CostPer1MOut/1e6*float64(usage.OutputTokens)
 
 	sess.Cost += cost
-	sess.CompletionTokens += usage.OutputTokens
-	sess.PromptTokens += usage.InputTokens
+	sess.CompletionTokens = usage.OutputTokens + usage.CacheReadTokens
+	sess.PromptTokens = usage.InputTokens + usage.CacheCreationTokens
 
 	_, err = a.sessions.Update(ctx, sess)
 	if err != nil {
@@ -646,7 +599,16 @@ func (a *agent) CompactSession(ctx context.Context, sessionID string) error {
 			Role: message.System,
 			Parts: []message.ContentPart{
 				message.TextContent{
-					Text: "You are a helpful AI assistant tasked with summarizing conversations.",
+					Text: `You are a helpful AI assistant tasked with summarizing conversations.
+
+When asked to summarize, provide a detailed but concise summary of the conversation. 
+Focus on information that would be helpful for continuing the conversation, including:
+- What was done
+- What is currently being worked on
+- Which files are being modified
+- What needs to be done next
+
+Your summary should be comprehensive enough to provide context but concise enough to be quickly understood.`,
 				},
 			},
 		},
@@ -655,7 +617,7 @@ func (a *agent) CompactSession(ctx context.Context, sessionID string) error {
 	// If there's an existing summary, include it
 	if existingSummary != "" {
 		messages = append(messages, message.Message{
-			Role: message.Assistant, // TODO: should this be system or user instead?
+			Role: message.Assistant,
 			Parts: []message.ContentPart{
 				message.TextContent{
 					Text: existingSummary,

+ 0 - 1
internal/llm/provider/bedrock.go

@@ -98,4 +98,3 @@ func (b *bedrockClient) stream(ctx context.Context, messages []message.Message,
 
 	return b.childProvider.stream(ctx, messages, tools)
 }
-

+ 23 - 23
internal/llm/tools/ls_test.go

@@ -83,19 +83,19 @@ func TestLsTool_Run(t *testing.T) {
 
 		response, err := tool.Run(context.Background(), call)
 		require.NoError(t, err)
-		
+
 		// Check that visible directories and files are included
 		assert.Contains(t, response.Content, "dir1")
 		assert.Contains(t, response.Content, "dir2")
 		assert.Contains(t, response.Content, "dir3")
 		assert.Contains(t, response.Content, "file1.txt")
 		assert.Contains(t, response.Content, "file2.txt")
-		
+
 		// Check that hidden files and directories are not included
 		assert.NotContains(t, response.Content, ".hidden_dir")
 		assert.NotContains(t, response.Content, ".hidden_file.txt")
 		assert.NotContains(t, response.Content, ".hidden_root_file.txt")
-		
+
 		// Check that __pycache__ is not included
 		assert.NotContains(t, response.Content, "__pycache__")
 	})
@@ -122,7 +122,7 @@ func TestLsTool_Run(t *testing.T) {
 	t.Run("handles empty path parameter", func(t *testing.T) {
 		// For this test, we need to mock the config.WorkingDirectory function
 		// Since we can't easily do that, we'll just check that the response doesn't contain an error message
-		
+
 		tool := NewLsTool()
 		params := LSParams{
 			Path: "",
@@ -138,7 +138,7 @@ func TestLsTool_Run(t *testing.T) {
 
 		response, err := tool.Run(context.Background(), call)
 		require.NoError(t, err)
-		
+
 		// The response should either contain a valid directory listing or an error
 		// We'll just check that it's not empty
 		assert.NotEmpty(t, response.Content)
@@ -173,11 +173,11 @@ func TestLsTool_Run(t *testing.T) {
 
 		response, err := tool.Run(context.Background(), call)
 		require.NoError(t, err)
-		
+
 		// The output format is a tree, so we need to check for specific patterns
 		// Check that file1.txt is not directly mentioned
 		assert.NotContains(t, response.Content, "- file1.txt")
-		
+
 		// Check that dir1/ is not directly mentioned
 		assert.NotContains(t, response.Content, "- dir1/")
 	})
@@ -189,12 +189,12 @@ func TestLsTool_Run(t *testing.T) {
 		defer func() {
 			os.Chdir(origWd)
 		}()
-		
+
 		// Change to a directory above the temp directory
 		parentDir := filepath.Dir(tempDir)
 		err = os.Chdir(parentDir)
 		require.NoError(t, err)
-		
+
 		tool := NewLsTool()
 		params := LSParams{
 			Path: filepath.Base(tempDir),
@@ -210,7 +210,7 @@ func TestLsTool_Run(t *testing.T) {
 
 		response, err := tool.Run(context.Background(), call)
 		require.NoError(t, err)
-		
+
 		// Should list the temp directory contents
 		assert.Contains(t, response.Content, "dir1")
 		assert.Contains(t, response.Content, "file1.txt")
@@ -291,22 +291,22 @@ func TestCreateFileTree(t *testing.T) {
 	}
 
 	tree := createFileTree(paths)
-	
+
 	// Check the structure of the tree
 	assert.Len(t, tree, 1) // Should have one root node
-	
+
 	// Check the root node
 	rootNode := tree[0]
 	assert.Equal(t, "path", rootNode.Name)
 	assert.Equal(t, "directory", rootNode.Type)
 	assert.Len(t, rootNode.Children, 1)
-	
+
 	// Check the "to" node
 	toNode := rootNode.Children[0]
 	assert.Equal(t, "to", toNode.Name)
 	assert.Equal(t, "directory", toNode.Type)
 	assert.Len(t, toNode.Children, 3) // file1.txt, dir1, dir2
-	
+
 	// Find the dir1 node
 	var dir1Node *TreeNode
 	for _, child := range toNode.Children {
@@ -315,7 +315,7 @@ func TestCreateFileTree(t *testing.T) {
 			break
 		}
 	}
-	
+
 	require.NotNil(t, dir1Node)
 	assert.Equal(t, "directory", dir1Node.Type)
 	assert.Len(t, dir1Node.Children, 2) // file2.txt and subdir
@@ -354,9 +354,9 @@ func TestPrintTree(t *testing.T) {
 			Type: "file",
 		},
 	}
-	
+
 	result := printTree(tree, "/root")
-	
+
 	// Check the output format
 	assert.Contains(t, result, "- /root/")
 	assert.Contains(t, result, "  - dir1/")
@@ -405,7 +405,7 @@ func TestListDirectory(t *testing.T) {
 		files, truncated, err := listDirectory(tempDir, []string{}, 1000)
 		require.NoError(t, err)
 		assert.False(t, truncated)
-		
+
 		// Check that visible files and directories are included
 		containsPath := func(paths []string, target string) bool {
 			targetPath := filepath.Join(tempDir, target)
@@ -416,12 +416,12 @@ func TestListDirectory(t *testing.T) {
 			}
 			return false
 		}
-		
+
 		assert.True(t, containsPath(files, "dir1"))
 		assert.True(t, containsPath(files, "file1.txt"))
 		assert.True(t, containsPath(files, "file2.txt"))
 		assert.True(t, containsPath(files, "dir1/file3.txt"))
-		
+
 		// Check that hidden files and directories are not included
 		assert.False(t, containsPath(files, ".hidden_dir"))
 		assert.False(t, containsPath(files, ".hidden_file.txt"))
@@ -438,12 +438,12 @@ func TestListDirectory(t *testing.T) {
 		files, truncated, err := listDirectory(tempDir, []string{"*.txt"}, 1000)
 		require.NoError(t, err)
 		assert.False(t, truncated)
-		
+
 		// Check that no .txt files are included
 		for _, file := range files {
 			assert.False(t, strings.HasSuffix(file, ".txt"), "Found .txt file: %s", file)
 		}
-		
+
 		// But directories should still be included
 		containsDir := false
 		for _, file := range files {
@@ -454,4 +454,4 @@ func TestListDirectory(t *testing.T) {
 		}
 		assert.True(t, containsDir)
 	})
-}
+}

+ 0 - 11
internal/logging/logging.go

@@ -11,7 +11,6 @@ import (
 	"os"
 	"runtime/debug"
 	"strings"
-	"sync"
 	"time"
 
 	"github.com/go-logfmt/logfmt"
@@ -45,7 +44,6 @@ type Service interface {
 type service struct {
 	db     *db.Queries
 	broker *pubsub.Broker[Log]
-	mu     sync.RWMutex
 }
 
 var globalLoggingService *service
@@ -72,9 +70,6 @@ func GetService() Service {
 }
 
 func (s *service) Create(ctx context.Context, log Log) error {
-	s.mu.Lock()
-	defer s.mu.Unlock()
-
 	if log.ID == "" {
 		log.ID = uuid.New().String()
 	}
@@ -115,9 +110,6 @@ func (s *service) Create(ctx context.Context, log Log) error {
 }
 
 func (s *service) ListBySession(ctx context.Context, sessionID string) ([]Log, error) {
-	s.mu.RLock()
-	defer s.mu.RUnlock()
-
 	dbLogs, err := s.db.ListLogsBySession(ctx, sql.NullString{String: sessionID, Valid: true})
 	if err != nil {
 		return nil, fmt.Errorf("db.ListLogsBySession: %w", err)
@@ -126,9 +118,6 @@ func (s *service) ListBySession(ctx context.Context, sessionID string) ([]Log, e
 }
 
 func (s *service) ListAll(ctx context.Context, limit int) ([]Log, error) {
-	s.mu.RLock()
-	defer s.mu.RUnlock()
-
 	dbLogs, err := s.db.ListAllLogs(ctx, int64(limit))
 	if err != nil {
 		return nil, fmt.Errorf("db.ListAllLogs: %w", err)

+ 0 - 1
internal/lsp/discovery/language.go

@@ -296,4 +296,3 @@ func GetLanguageIDFromPath(path string) string {
 	langKind := lsp.DetectLanguageID(uri)
 	return GetLanguageIDFromProtocol(string(langKind))
 }
-

+ 0 - 1
internal/lsp/discovery/server.go

@@ -304,4 +304,3 @@ func ConfigureLSPServers(rootDir string) (map[string]ServerInfo, error) {
 
 	return servers, nil
 }
-

+ 0 - 1
internal/pubsub/broker_test.go

@@ -142,4 +142,3 @@ func TestBrokerConcurrency(t *testing.T) {
 	}
 	assert.Equal(t, numSubscribers, count)
 }
-

+ 0 - 1
internal/tui/components/dialog/arguments.go

@@ -170,4 +170,3 @@ type ShowArgumentsDialogMsg struct {
 	CommandID string
 	Content   string
 }
-

+ 1 - 1
internal/tui/components/dialog/commands.go

@@ -115,7 +115,7 @@ func (c *commandDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 func (c *commandDialogCmp) View() string {
 	t := theme.CurrentTheme()
 	baseStyle := styles.BaseStyle()
-	
+
 	if len(c.commands) == 0 {
 		return baseStyle.Padding(1, 2).
 			Border(lipgloss.RoundedBorder()).

+ 1 - 1
internal/tui/components/dialog/init.go

@@ -95,7 +95,7 @@ func (m InitDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 func (m InitDialogCmp) View() string {
 	t := theme.CurrentTheme()
 	baseStyle := styles.BaseStyle()
-	
+
 	// Calculate width needed for content
 	maxWidth := 60 // Width for explanation text
 

+ 9 - 9
internal/tui/components/dialog/permission.go

@@ -150,7 +150,7 @@ func (p *permissionDialogCmp) selectCurrentOption() tea.Cmd {
 func (p *permissionDialogCmp) renderButtons() string {
 	t := theme.CurrentTheme()
 	baseStyle := styles.BaseStyle()
-	
+
 	allowStyle := baseStyle
 	allowSessionStyle := baseStyle
 	denyStyle := baseStyle
@@ -196,7 +196,7 @@ func (p *permissionDialogCmp) renderButtons() string {
 func (p *permissionDialogCmp) renderHeader() string {
 	t := theme.CurrentTheme()
 	baseStyle := styles.BaseStyle()
-	
+
 	toolKey := baseStyle.Foreground(t.TextMuted()).Bold(true).Render("Tool")
 	toolValue := baseStyle.
 		Foreground(t.Text()).
@@ -242,13 +242,13 @@ func (p *permissionDialogCmp) renderHeader() string {
 func (p *permissionDialogCmp) renderBashContent() string {
 	t := theme.CurrentTheme()
 	baseStyle := styles.BaseStyle()
-	
+
 	if pr, ok := p.permission.Params.(tools.BashPermissionsParams); ok {
 		content := fmt.Sprintf("```bash\n%s\n```", pr.Command)
 
 		// Use the cache for markdown rendering
 		renderedContent := p.GetOrSetMarkdown(p.permission.ID, func() (string, error) {
-			r := styles.GetMarkdownRenderer(p.width-10)
+			r := styles.GetMarkdownRenderer(p.width - 10)
 			s, err := r.Render(content)
 			return styles.ForceReplaceBackgroundWithLipgloss(s, t.Background()), err
 		})
@@ -302,13 +302,13 @@ func (p *permissionDialogCmp) renderWriteContent() string {
 func (p *permissionDialogCmp) renderFetchContent() string {
 	t := theme.CurrentTheme()
 	baseStyle := styles.BaseStyle()
-	
+
 	if pr, ok := p.permission.Params.(tools.FetchPermissionsParams); ok {
 		content := fmt.Sprintf("```bash\n%s\n```", pr.URL)
 
 		// Use the cache for markdown rendering
 		renderedContent := p.GetOrSetMarkdown(p.permission.ID, func() (string, error) {
-			r := styles.GetMarkdownRenderer(p.width-10)
+			r := styles.GetMarkdownRenderer(p.width - 10)
 			s, err := r.Render(content)
 			return styles.ForceReplaceBackgroundWithLipgloss(s, t.Background()), err
 		})
@@ -325,12 +325,12 @@ func (p *permissionDialogCmp) renderFetchContent() string {
 func (p *permissionDialogCmp) renderDefaultContent() string {
 	t := theme.CurrentTheme()
 	baseStyle := styles.BaseStyle()
-	
+
 	content := p.permission.Description
 
 	// Use the cache for markdown rendering
 	renderedContent := p.GetOrSetMarkdown(p.permission.ID, func() (string, error) {
-		r := styles.GetMarkdownRenderer(p.width-10)
+		r := styles.GetMarkdownRenderer(p.width - 10)
 		s, err := r.Render(content)
 		return styles.ForceReplaceBackgroundWithLipgloss(s, t.Background()), err
 	})
@@ -358,7 +358,7 @@ func (p *permissionDialogCmp) styleViewport() string {
 func (p *permissionDialogCmp) render() string {
 	t := theme.CurrentTheme()
 	baseStyle := styles.BaseStyle()
-	
+
 	title := baseStyle.
 		Bold(true).
 		Width(p.width - 4).

+ 1 - 1
internal/tui/components/dialog/quit.go

@@ -84,7 +84,7 @@ func (q *quitDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 func (q *quitDialogCmp) View() string {
 	t := theme.CurrentTheme()
 	baseStyle := styles.BaseStyle()
-	
+
 	yesStyle := baseStyle
 	noStyle := baseStyle
 	spacerStyle := baseStyle.Background(t.Background())

+ 1 - 1
internal/tui/components/logs/details.go

@@ -96,7 +96,7 @@ func (i *detailCmp) updateContent() {
 		valueStyle := lipgloss.NewStyle().Foreground(t.Text())
 
 		for key, value := range i.currentLog.Attributes {
-			attrLine := fmt.Sprintf("%s: %s", 
+			attrLine := fmt.Sprintf("%s: %s",
 				keyStyle.Render(key),
 				valueStyle.Render(value),
 			)

+ 2 - 2
internal/tui/layout/container.go

@@ -31,7 +31,7 @@ type container struct {
 	borderBottom bool
 	borderLeft   bool
 	borderStyle  lipgloss.Border
-	
+
 	focused bool // Track focus state
 }
 
@@ -69,7 +69,7 @@ func (c *container) View() string {
 			width--
 		}
 		style = style.Border(c.borderStyle, c.borderTop, c.borderRight, c.borderBottom, c.borderLeft)
-		
+
 		// Use primary color for border if focused
 		if c.focused {
 			style = style.BorderBackground(t.Background()).BorderForeground(t.Primary())

+ 2 - 2
internal/tui/page/logs.go

@@ -196,7 +196,7 @@ func (p *logsPage) Init() tea.Cmd {
 	var cmds []tea.Cmd
 	cmds = append(cmds, p.table.Init())
 	cmds = append(cmds, p.details.Init())
-	
+
 	// Send a key down and then key up to select the first row
 	// This ensures the details pane is populated when returning to the logs page
 	cmds = append(cmds, func() tea.Msg {
@@ -205,7 +205,7 @@ func (p *logsPage) Init() tea.Cmd {
 	cmds = append(cmds, func() tea.Msg {
 		return tea.KeyMsg{Type: tea.KeyUp}
 	})
-	
+
 	return tea.Batch(cmds...)
 }
 

+ 1 - 1
internal/tui/theme/catppuccin.go

@@ -245,4 +245,4 @@ func NewCatppuccinTheme() *CatppuccinTheme {
 func init() {
 	// Register the Catppuccin theme with the theme manager
 	RegisterTheme("catppuccin", NewCatppuccinTheme())
-}
+}

+ 1 - 1
internal/tui/theme/dracula.go

@@ -271,4 +271,4 @@ func NewDraculaTheme() *DraculaTheme {
 func init() {
 	// Register the Dracula theme with the theme manager
 	RegisterTheme("dracula", NewDraculaTheme())
-}
+}

+ 15 - 15
internal/tui/theme/flexoki.go

@@ -7,20 +7,20 @@ import (
 // Flexoki color palette constants
 const (
 	// Base colors
-	flexokiPaper    = "#FFFCF0" // Paper (lightest)
-	flexokiBase50   = "#F2F0E5" // bg-2 (light)
-	flexokiBase100  = "#E6E4D9" // ui (light)
-	flexokiBase150  = "#DAD8CE" // ui-2 (light)
-	flexokiBase200  = "#CECDC3" // ui-3 (light)
-	flexokiBase300  = "#B7B5AC" // tx-3 (light)
-	flexokiBase500  = "#878580" // tx-2 (light)
-	flexokiBase600  = "#6F6E69" // tx (light)
-	flexokiBase700  = "#575653" // tx-3 (dark)
-	flexokiBase800  = "#403E3C" // ui-3 (dark)
-	flexokiBase850  = "#343331" // ui-2 (dark)
-	flexokiBase900  = "#282726" // ui (dark)
-	flexokiBase950  = "#1C1B1A" // bg-2 (dark)
-	flexokiBlack    = "#100F0F" // bg (darkest)
+	flexokiPaper   = "#FFFCF0" // Paper (lightest)
+	flexokiBase50  = "#F2F0E5" // bg-2 (light)
+	flexokiBase100 = "#E6E4D9" // ui (light)
+	flexokiBase150 = "#DAD8CE" // ui-2 (light)
+	flexokiBase200 = "#CECDC3" // ui-3 (light)
+	flexokiBase300 = "#B7B5AC" // tx-3 (light)
+	flexokiBase500 = "#878580" // tx-2 (light)
+	flexokiBase600 = "#6F6E69" // tx (light)
+	flexokiBase700 = "#575653" // tx-3 (dark)
+	flexokiBase800 = "#403E3C" // ui-3 (dark)
+	flexokiBase850 = "#343331" // ui-2 (dark)
+	flexokiBase900 = "#282726" // ui (dark)
+	flexokiBase950 = "#1C1B1A" // bg-2 (dark)
+	flexokiBlack   = "#100F0F" // bg (darkest)
 
 	// Accent colors - Light theme (600)
 	flexokiRed600     = "#AF3029"
@@ -279,4 +279,4 @@ func NewFlexokiTheme() *FlexokiTheme {
 func init() {
 	// Register the Flexoki theme with the theme manager
 	RegisterTheme("flexoki", NewFlexokiTheme())
-}
+}

+ 5 - 5
internal/tui/theme/gruvbox.go

@@ -173,11 +173,11 @@ func NewGruvboxTheme() *GruvboxTheme {
 		Light: gruvboxLightRedBright,
 	}
 	theme.DiffAddedBgColor = lipgloss.AdaptiveColor{
-		Dark:  "#3C4C3C",  // Darker green background
+		Dark:  "#3C4C3C", // Darker green background
 		Light: "#E8F5E9", // Light green background
 	}
 	theme.DiffRemovedBgColor = lipgloss.AdaptiveColor{
-		Dark:  "#4C3C3C",  // Darker red background
+		Dark:  "#4C3C3C", // Darker red background
 		Light: "#FFEBEE", // Light red background
 	}
 	theme.DiffContextBgColor = lipgloss.AdaptiveColor{
@@ -189,11 +189,11 @@ func NewGruvboxTheme() *GruvboxTheme {
 		Light: gruvboxLightFg4,
 	}
 	theme.DiffAddedLineNumberBgColor = lipgloss.AdaptiveColor{
-		Dark:  "#32432F",   // Slightly darker green
+		Dark:  "#32432F", // Slightly darker green
 		Light: "#C8E6C9", // Light green
 	}
 	theme.DiffRemovedLineNumberBgColor = lipgloss.AdaptiveColor{
-		Dark:  "#43322F",   // Slightly darker red
+		Dark:  "#43322F", // Slightly darker red
 		Light: "#FFCDD2", // Light red
 	}
 
@@ -299,4 +299,4 @@ func NewGruvboxTheme() *GruvboxTheme {
 func init() {
 	// Register the Gruvbox theme with the theme manager
 	RegisterTheme("gruvbox", NewGruvboxTheme())
-}
+}

+ 1 - 1
internal/tui/theme/monokai.go

@@ -270,4 +270,4 @@ func NewMonokaiProTheme() *MonokaiProTheme {
 func init() {
 	// Register the Monokai Pro theme with the theme manager
 	RegisterTheme("monokai", NewMonokaiProTheme())
-}
+}

+ 1 - 1
internal/tui/theme/onedark.go

@@ -271,4 +271,4 @@ func NewOneDarkTheme() *OneDarkTheme {
 func init() {
 	// Register the One Dark theme with the theme manager
 	RegisterTheme("onedark", NewOneDarkTheme())
-}
+}

+ 0 - 1
internal/tui/theme/opencode.go

@@ -274,4 +274,3 @@ func init() {
 	// Register the OpenCode theme with the theme manager
 	RegisterTheme("opencode", NewOpenCodeTheme())
 }
-

+ 92 - 84
internal/tui/theme/theme.go

@@ -83,25 +83,25 @@ type Theme interface {
 // that can be embedded in concrete theme implementations.
 type BaseTheme struct {
 	// Base colors
-	PrimaryColor       lipgloss.AdaptiveColor
-	SecondaryColor     lipgloss.AdaptiveColor
-	AccentColor        lipgloss.AdaptiveColor
+	PrimaryColor   lipgloss.AdaptiveColor
+	SecondaryColor lipgloss.AdaptiveColor
+	AccentColor    lipgloss.AdaptiveColor
 
 	// Status colors
-	ErrorColor         lipgloss.AdaptiveColor
-	WarningColor       lipgloss.AdaptiveColor
-	SuccessColor       lipgloss.AdaptiveColor
-	InfoColor          lipgloss.AdaptiveColor
+	ErrorColor   lipgloss.AdaptiveColor
+	WarningColor lipgloss.AdaptiveColor
+	SuccessColor lipgloss.AdaptiveColor
+	InfoColor    lipgloss.AdaptiveColor
 
 	// Text colors
-	TextColor          lipgloss.AdaptiveColor
-	TextMutedColor     lipgloss.AdaptiveColor
+	TextColor           lipgloss.AdaptiveColor
+	TextMutedColor      lipgloss.AdaptiveColor
 	TextEmphasizedColor lipgloss.AdaptiveColor
 
 	// Background colors
-	BackgroundColor    lipgloss.AdaptiveColor
+	BackgroundColor          lipgloss.AdaptiveColor
 	BackgroundSecondaryColor lipgloss.AdaptiveColor
-	BackgroundDarkerColor lipgloss.AdaptiveColor
+	BackgroundDarkerColor    lipgloss.AdaptiveColor
 
 	// Border colors
 	BorderNormalColor  lipgloss.AdaptiveColor
@@ -109,105 +109,113 @@ type BaseTheme struct {
 	BorderDimColor     lipgloss.AdaptiveColor
 
 	// Diff view colors
-	DiffAddedColor     lipgloss.AdaptiveColor
-	DiffRemovedColor   lipgloss.AdaptiveColor
-	DiffContextColor   lipgloss.AdaptiveColor
-	DiffHunkHeaderColor lipgloss.AdaptiveColor
-	DiffHighlightAddedColor lipgloss.AdaptiveColor
-	DiffHighlightRemovedColor lipgloss.AdaptiveColor
-	DiffAddedBgColor   lipgloss.AdaptiveColor
-	DiffRemovedBgColor lipgloss.AdaptiveColor
-	DiffContextBgColor lipgloss.AdaptiveColor
-	DiffLineNumberColor lipgloss.AdaptiveColor
-	DiffAddedLineNumberBgColor lipgloss.AdaptiveColor
+	DiffAddedColor               lipgloss.AdaptiveColor
+	DiffRemovedColor             lipgloss.AdaptiveColor
+	DiffContextColor             lipgloss.AdaptiveColor
+	DiffHunkHeaderColor          lipgloss.AdaptiveColor
+	DiffHighlightAddedColor      lipgloss.AdaptiveColor
+	DiffHighlightRemovedColor    lipgloss.AdaptiveColor
+	DiffAddedBgColor             lipgloss.AdaptiveColor
+	DiffRemovedBgColor           lipgloss.AdaptiveColor
+	DiffContextBgColor           lipgloss.AdaptiveColor
+	DiffLineNumberColor          lipgloss.AdaptiveColor
+	DiffAddedLineNumberBgColor   lipgloss.AdaptiveColor
 	DiffRemovedLineNumberBgColor lipgloss.AdaptiveColor
 
 	// Markdown colors
-	MarkdownTextColor  lipgloss.AdaptiveColor
-	MarkdownHeadingColor lipgloss.AdaptiveColor
-	MarkdownLinkColor  lipgloss.AdaptiveColor
-	MarkdownLinkTextColor lipgloss.AdaptiveColor
-	MarkdownCodeColor  lipgloss.AdaptiveColor
-	MarkdownBlockQuoteColor lipgloss.AdaptiveColor
-	MarkdownEmphColor  lipgloss.AdaptiveColor
-	MarkdownStrongColor lipgloss.AdaptiveColor
-	MarkdownHorizontalRuleColor lipgloss.AdaptiveColor
-	MarkdownListItemColor lipgloss.AdaptiveColor
+	MarkdownTextColor            lipgloss.AdaptiveColor
+	MarkdownHeadingColor         lipgloss.AdaptiveColor
+	MarkdownLinkColor            lipgloss.AdaptiveColor
+	MarkdownLinkTextColor        lipgloss.AdaptiveColor
+	MarkdownCodeColor            lipgloss.AdaptiveColor
+	MarkdownBlockQuoteColor      lipgloss.AdaptiveColor
+	MarkdownEmphColor            lipgloss.AdaptiveColor
+	MarkdownStrongColor          lipgloss.AdaptiveColor
+	MarkdownHorizontalRuleColor  lipgloss.AdaptiveColor
+	MarkdownListItemColor        lipgloss.AdaptiveColor
 	MarkdownListEnumerationColor lipgloss.AdaptiveColor
-	MarkdownImageColor lipgloss.AdaptiveColor
-	MarkdownImageTextColor lipgloss.AdaptiveColor
-	MarkdownCodeBlockColor lipgloss.AdaptiveColor
+	MarkdownImageColor           lipgloss.AdaptiveColor
+	MarkdownImageTextColor       lipgloss.AdaptiveColor
+	MarkdownCodeBlockColor       lipgloss.AdaptiveColor
 
 	// Syntax highlighting colors
-	SyntaxCommentColor lipgloss.AdaptiveColor
-	SyntaxKeywordColor lipgloss.AdaptiveColor
-	SyntaxFunctionColor lipgloss.AdaptiveColor
-	SyntaxVariableColor lipgloss.AdaptiveColor
-	SyntaxStringColor  lipgloss.AdaptiveColor
-	SyntaxNumberColor  lipgloss.AdaptiveColor
-	SyntaxTypeColor    lipgloss.AdaptiveColor
-	SyntaxOperatorColor lipgloss.AdaptiveColor
+	SyntaxCommentColor     lipgloss.AdaptiveColor
+	SyntaxKeywordColor     lipgloss.AdaptiveColor
+	SyntaxFunctionColor    lipgloss.AdaptiveColor
+	SyntaxVariableColor    lipgloss.AdaptiveColor
+	SyntaxStringColor      lipgloss.AdaptiveColor
+	SyntaxNumberColor      lipgloss.AdaptiveColor
+	SyntaxTypeColor        lipgloss.AdaptiveColor
+	SyntaxOperatorColor    lipgloss.AdaptiveColor
 	SyntaxPunctuationColor lipgloss.AdaptiveColor
 }
 
 // Implement the Theme interface for BaseTheme
-func (t *BaseTheme) Primary() lipgloss.AdaptiveColor { return t.PrimaryColor }
+func (t *BaseTheme) Primary() lipgloss.AdaptiveColor   { return t.PrimaryColor }
 func (t *BaseTheme) Secondary() lipgloss.AdaptiveColor { return t.SecondaryColor }
-func (t *BaseTheme) Accent() lipgloss.AdaptiveColor { return t.AccentColor }
+func (t *BaseTheme) Accent() lipgloss.AdaptiveColor    { return t.AccentColor }
 
-func (t *BaseTheme) Error() lipgloss.AdaptiveColor { return t.ErrorColor }
+func (t *BaseTheme) Error() lipgloss.AdaptiveColor   { return t.ErrorColor }
 func (t *BaseTheme) Warning() lipgloss.AdaptiveColor { return t.WarningColor }
 func (t *BaseTheme) Success() lipgloss.AdaptiveColor { return t.SuccessColor }
-func (t *BaseTheme) Info() lipgloss.AdaptiveColor { return t.InfoColor }
+func (t *BaseTheme) Info() lipgloss.AdaptiveColor    { return t.InfoColor }
 
-func (t *BaseTheme) Text() lipgloss.AdaptiveColor { return t.TextColor }
-func (t *BaseTheme) TextMuted() lipgloss.AdaptiveColor { return t.TextMutedColor }
+func (t *BaseTheme) Text() lipgloss.AdaptiveColor           { return t.TextColor }
+func (t *BaseTheme) TextMuted() lipgloss.AdaptiveColor      { return t.TextMutedColor }
 func (t *BaseTheme) TextEmphasized() lipgloss.AdaptiveColor { return t.TextEmphasizedColor }
 
-func (t *BaseTheme) Background() lipgloss.AdaptiveColor { return t.BackgroundColor }
+func (t *BaseTheme) Background() lipgloss.AdaptiveColor          { return t.BackgroundColor }
 func (t *BaseTheme) BackgroundSecondary() lipgloss.AdaptiveColor { return t.BackgroundSecondaryColor }
-func (t *BaseTheme) BackgroundDarker() lipgloss.AdaptiveColor { return t.BackgroundDarkerColor }
+func (t *BaseTheme) BackgroundDarker() lipgloss.AdaptiveColor    { return t.BackgroundDarkerColor }
 
-func (t *BaseTheme) BorderNormal() lipgloss.AdaptiveColor { return t.BorderNormalColor }
+func (t *BaseTheme) BorderNormal() lipgloss.AdaptiveColor  { return t.BorderNormalColor }
 func (t *BaseTheme) BorderFocused() lipgloss.AdaptiveColor { return t.BorderFocusedColor }
-func (t *BaseTheme) BorderDim() lipgloss.AdaptiveColor { return t.BorderDimColor }
+func (t *BaseTheme) BorderDim() lipgloss.AdaptiveColor     { return t.BorderDimColor }
 
-func (t *BaseTheme) DiffAdded() lipgloss.AdaptiveColor { return t.DiffAddedColor }
-func (t *BaseTheme) DiffRemoved() lipgloss.AdaptiveColor { return t.DiffRemovedColor }
-func (t *BaseTheme) DiffContext() lipgloss.AdaptiveColor { return t.DiffContextColor }
-func (t *BaseTheme) DiffHunkHeader() lipgloss.AdaptiveColor { return t.DiffHunkHeaderColor }
-func (t *BaseTheme) DiffHighlightAdded() lipgloss.AdaptiveColor { return t.DiffHighlightAddedColor }
+func (t *BaseTheme) DiffAdded() lipgloss.AdaptiveColor            { return t.DiffAddedColor }
+func (t *BaseTheme) DiffRemoved() lipgloss.AdaptiveColor          { return t.DiffRemovedColor }
+func (t *BaseTheme) DiffContext() lipgloss.AdaptiveColor          { return t.DiffContextColor }
+func (t *BaseTheme) DiffHunkHeader() lipgloss.AdaptiveColor       { return t.DiffHunkHeaderColor }
+func (t *BaseTheme) DiffHighlightAdded() lipgloss.AdaptiveColor   { return t.DiffHighlightAddedColor }
 func (t *BaseTheme) DiffHighlightRemoved() lipgloss.AdaptiveColor { return t.DiffHighlightRemovedColor }
-func (t *BaseTheme) DiffAddedBg() lipgloss.AdaptiveColor { return t.DiffAddedBgColor }
-func (t *BaseTheme) DiffRemovedBg() lipgloss.AdaptiveColor { return t.DiffRemovedBgColor }
-func (t *BaseTheme) DiffContextBg() lipgloss.AdaptiveColor { return t.DiffContextBgColor }
-func (t *BaseTheme) DiffLineNumber() lipgloss.AdaptiveColor { return t.DiffLineNumberColor }
-func (t *BaseTheme) DiffAddedLineNumberBg() lipgloss.AdaptiveColor { return t.DiffAddedLineNumberBgColor }
-func (t *BaseTheme) DiffRemovedLineNumberBg() lipgloss.AdaptiveColor { return t.DiffRemovedLineNumberBgColor }
-
-func (t *BaseTheme) MarkdownText() lipgloss.AdaptiveColor { return t.MarkdownTextColor }
-func (t *BaseTheme) MarkdownHeading() lipgloss.AdaptiveColor { return t.MarkdownHeadingColor }
-func (t *BaseTheme) MarkdownLink() lipgloss.AdaptiveColor { return t.MarkdownLinkColor }
-func (t *BaseTheme) MarkdownLinkText() lipgloss.AdaptiveColor { return t.MarkdownLinkTextColor }
-func (t *BaseTheme) MarkdownCode() lipgloss.AdaptiveColor { return t.MarkdownCodeColor }
+func (t *BaseTheme) DiffAddedBg() lipgloss.AdaptiveColor          { return t.DiffAddedBgColor }
+func (t *BaseTheme) DiffRemovedBg() lipgloss.AdaptiveColor        { return t.DiffRemovedBgColor }
+func (t *BaseTheme) DiffContextBg() lipgloss.AdaptiveColor        { return t.DiffContextBgColor }
+func (t *BaseTheme) DiffLineNumber() lipgloss.AdaptiveColor       { return t.DiffLineNumberColor }
+func (t *BaseTheme) DiffAddedLineNumberBg() lipgloss.AdaptiveColor {
+	return t.DiffAddedLineNumberBgColor
+}
+func (t *BaseTheme) DiffRemovedLineNumberBg() lipgloss.AdaptiveColor {
+	return t.DiffRemovedLineNumberBgColor
+}
+
+func (t *BaseTheme) MarkdownText() lipgloss.AdaptiveColor       { return t.MarkdownTextColor }
+func (t *BaseTheme) MarkdownHeading() lipgloss.AdaptiveColor    { return t.MarkdownHeadingColor }
+func (t *BaseTheme) MarkdownLink() lipgloss.AdaptiveColor       { return t.MarkdownLinkColor }
+func (t *BaseTheme) MarkdownLinkText() lipgloss.AdaptiveColor   { return t.MarkdownLinkTextColor }
+func (t *BaseTheme) MarkdownCode() lipgloss.AdaptiveColor       { return t.MarkdownCodeColor }
 func (t *BaseTheme) MarkdownBlockQuote() lipgloss.AdaptiveColor { return t.MarkdownBlockQuoteColor }
-func (t *BaseTheme) MarkdownEmph() lipgloss.AdaptiveColor { return t.MarkdownEmphColor }
-func (t *BaseTheme) MarkdownStrong() lipgloss.AdaptiveColor { return t.MarkdownStrongColor }
-func (t *BaseTheme) MarkdownHorizontalRule() lipgloss.AdaptiveColor { return t.MarkdownHorizontalRuleColor }
+func (t *BaseTheme) MarkdownEmph() lipgloss.AdaptiveColor       { return t.MarkdownEmphColor }
+func (t *BaseTheme) MarkdownStrong() lipgloss.AdaptiveColor     { return t.MarkdownStrongColor }
+func (t *BaseTheme) MarkdownHorizontalRule() lipgloss.AdaptiveColor {
+	return t.MarkdownHorizontalRuleColor
+}
 func (t *BaseTheme) MarkdownListItem() lipgloss.AdaptiveColor { return t.MarkdownListItemColor }
-func (t *BaseTheme) MarkdownListEnumeration() lipgloss.AdaptiveColor { return t.MarkdownListEnumerationColor }
-func (t *BaseTheme) MarkdownImage() lipgloss.AdaptiveColor { return t.MarkdownImageColor }
+func (t *BaseTheme) MarkdownListEnumeration() lipgloss.AdaptiveColor {
+	return t.MarkdownListEnumerationColor
+}
+func (t *BaseTheme) MarkdownImage() lipgloss.AdaptiveColor     { return t.MarkdownImageColor }
 func (t *BaseTheme) MarkdownImageText() lipgloss.AdaptiveColor { return t.MarkdownImageTextColor }
 func (t *BaseTheme) MarkdownCodeBlock() lipgloss.AdaptiveColor { return t.MarkdownCodeBlockColor }
 
-func (t *BaseTheme) SyntaxComment() lipgloss.AdaptiveColor { return t.SyntaxCommentColor }
-func (t *BaseTheme) SyntaxKeyword() lipgloss.AdaptiveColor { return t.SyntaxKeywordColor }
-func (t *BaseTheme) SyntaxFunction() lipgloss.AdaptiveColor { return t.SyntaxFunctionColor }
-func (t *BaseTheme) SyntaxVariable() lipgloss.AdaptiveColor { return t.SyntaxVariableColor }
-func (t *BaseTheme) SyntaxString() lipgloss.AdaptiveColor { return t.SyntaxStringColor }
-func (t *BaseTheme) SyntaxNumber() lipgloss.AdaptiveColor { return t.SyntaxNumberColor }
-func (t *BaseTheme) SyntaxType() lipgloss.AdaptiveColor { return t.SyntaxTypeColor }
-func (t *BaseTheme) SyntaxOperator() lipgloss.AdaptiveColor { return t.SyntaxOperatorColor }
+func (t *BaseTheme) SyntaxComment() lipgloss.AdaptiveColor     { return t.SyntaxCommentColor }
+func (t *BaseTheme) SyntaxKeyword() lipgloss.AdaptiveColor     { return t.SyntaxKeywordColor }
+func (t *BaseTheme) SyntaxFunction() lipgloss.AdaptiveColor    { return t.SyntaxFunctionColor }
+func (t *BaseTheme) SyntaxVariable() lipgloss.AdaptiveColor    { return t.SyntaxVariableColor }
+func (t *BaseTheme) SyntaxString() lipgloss.AdaptiveColor      { return t.SyntaxStringColor }
+func (t *BaseTheme) SyntaxNumber() lipgloss.AdaptiveColor      { return t.SyntaxNumberColor }
+func (t *BaseTheme) SyntaxType() lipgloss.AdaptiveColor        { return t.SyntaxTypeColor }
+func (t *BaseTheme) SyntaxOperator() lipgloss.AdaptiveColor    { return t.SyntaxOperatorColor }
 func (t *BaseTheme) SyntaxPunctuation() lipgloss.AdaptiveColor { return t.SyntaxPunctuationColor }
 
 // ParseAdaptiveColor parses a color value from the config file into a lipgloss.AdaptiveColor.
@@ -254,4 +262,4 @@ func ParseAdaptiveColor(value any) (lipgloss.AdaptiveColor, error) {
 	}
 
 	return lipgloss.AdaptiveColor{}, fmt.Errorf("color must be either a hex string or an object with dark/light keys")
-}
+}

+ 16 - 16
internal/tui/theme/theme_test.go

@@ -7,7 +7,7 @@ import (
 func TestThemeRegistration(t *testing.T) {
 	// Get list of available themes
 	availableThemes := AvailableThemes()
-	
+
 	// Check if "catppuccin" theme is registered
 	catppuccinFound := false
 	for _, themeName := range availableThemes {
@@ -16,11 +16,11 @@ func TestThemeRegistration(t *testing.T) {
 			break
 		}
 	}
-	
+
 	if !catppuccinFound {
 		t.Errorf("Catppuccin theme is not registered")
 	}
-	
+
 	// Check if "gruvbox" theme is registered
 	gruvboxFound := false
 	for _, themeName := range availableThemes {
@@ -29,11 +29,11 @@ func TestThemeRegistration(t *testing.T) {
 			break
 		}
 	}
-	
+
 	if !gruvboxFound {
 		t.Errorf("Gruvbox theme is not registered")
 	}
-	
+
 	// Check if "monokai" theme is registered
 	monokaiFound := false
 	for _, themeName := range availableThemes {
@@ -42,48 +42,48 @@ func TestThemeRegistration(t *testing.T) {
 			break
 		}
 	}
-	
+
 	if !monokaiFound {
 		t.Errorf("Monokai theme is not registered")
 	}
-	
+
 	// Try to get the themes and make sure they're not nil
 	catppuccin := GetTheme("catppuccin")
 	if catppuccin == nil {
 		t.Errorf("Catppuccin theme is nil")
 	}
-	
+
 	gruvbox := GetTheme("gruvbox")
 	if gruvbox == nil {
 		t.Errorf("Gruvbox theme is nil")
 	}
-	
+
 	monokai := GetTheme("monokai")
 	if monokai == nil {
 		t.Errorf("Monokai theme is nil")
 	}
-	
+
 	// Test switching theme
 	originalTheme := CurrentThemeName()
-	
+
 	err := SetTheme("gruvbox")
 	if err != nil {
 		t.Errorf("Failed to set theme to gruvbox: %v", err)
 	}
-	
+
 	if CurrentThemeName() != "gruvbox" {
 		t.Errorf("Theme not properly switched to gruvbox")
 	}
-	
+
 	err = SetTheme("monokai")
 	if err != nil {
 		t.Errorf("Failed to set theme to monokai: %v", err)
 	}
-	
+
 	if CurrentThemeName() != "monokai" {
 		t.Errorf("Theme not properly switched to monokai")
 	}
-	
+
 	// Switch back to original theme
 	_ = SetTheme(originalTheme)
-}
+}

+ 1 - 1
internal/tui/theme/tokyonight.go

@@ -271,4 +271,4 @@ func NewTokyoNightTheme() *TokyoNightTheme {
 func init() {
 	// Register the Tokyo Night theme with the theme manager
 	RegisterTheme("tokyonight", NewTokyoNightTheme())
-}
+}

+ 1 - 1
internal/tui/theme/tron.go

@@ -273,4 +273,4 @@ func NewTronTheme() *TronTheme {
 func init() {
 	// Register the Tron theme with the theme manager
 	RegisterTheme("tron", NewTronTheme())
-}
+}