add ringbuffer tailing

Tai Groot created

Change summary

cmd/logs.go             | 62 +++++++++++++++++++++++++++++++++++++++++++
internal/config/load.go |  2 
2 files changed, 63 insertions(+), 1 deletion(-)

Detailed changes

cmd/logs.go 🔗

@@ -32,6 +32,11 @@ var logsCmd = &cobra.Command{
 			return fmt.Errorf("failed to get follow flag: %v", err)
 		}
 
+		tailLines, err := cmd.Flags().GetInt("tail")
+		if err != nil {
+			return fmt.Errorf("failed to get tail flag: %v", err)
+		}
+
 		log.SetLevel(log.DebugLevel)
 		// Configure log to output to stdout instead of stderr
 		log.SetOutput(os.Stdout)
@@ -58,6 +63,15 @@ var logsCmd = &cobra.Command{
 			for line := range t.Lines {
 				printLogLine(line.Text)
 			}
+		} else if tailLines > 0 {
+			// Tail mode - show last N lines
+			lines, err := readLastNLines(logsFile, tailLines)
+			if err != nil {
+				return fmt.Errorf("failed to read last %d lines: %v", tailLines, err)
+			}
+			for _, line := range lines {
+				printLogLine(line)
+			}
 		} else {
 			// Oneshot mode - read the entire file once
 			file, err := os.Open(logsFile)
@@ -88,9 +102,57 @@ var logsCmd = &cobra.Command{
 
 func init() {
 	logsCmd.Flags().BoolP("follow", "f", false, "Follow log output")
+	logsCmd.Flags().IntP("tail", "t", 0, "Show only the last N lines")
 	rootCmd.AddCommand(logsCmd)
 }
 
+// readLastNLines reads the last N lines from a file using a simple circular buffer approach
+func readLastNLines(filename string, n int) ([]string, error) {
+	file, err := os.Open(filename)
+	if err != nil {
+		return nil, err
+	}
+	defer file.Close()
+
+	// Use a circular buffer to keep only the last N lines
+	lines := make([]string, n)
+	count := 0
+	index := 0
+
+	reader := bufio.NewReader(file)
+	for {
+		line, err := reader.ReadString('\n')
+		if err != nil {
+			if err == io.EOF && line != "" {
+				// Handle last line without newline
+				line = strings.TrimSuffix(line, "\n")
+				lines[index] = line
+				count++
+				index = (index + 1) % n
+			}
+			break
+		}
+		// Remove trailing newline
+		line = strings.TrimSuffix(line, "\n")
+		lines[index] = line
+		count++
+		index = (index + 1) % n
+	}
+
+	// Extract the last N lines in correct order
+	if count <= n {
+		// We have fewer lines than requested, return them all
+		return lines[:count], nil
+	}
+
+	// We have more lines than requested, extract from circular buffer
+	result := make([]string, n)
+	for i := range n {
+		result[i] = lines[(index+i)%n]
+	}
+	return result, nil
+}
+
 func printLogLine(lineText string) {
 	var data map[string]any
 	if err := json.Unmarshal([]byte(lineText), &data); err != nil {

internal/config/load.go 🔗

@@ -388,7 +388,7 @@ func (cfg *Config) configureSelectedModels(knownProviders []provider.Provider) e
 		}
 		model := cfg.GetModel(large.Provider, large.Model)
 		slog.Info("Configuring selected large model", "provider", large.Provider, "model", large.Model)
-		slog.Info("MOdel configured", "model", model)
+		slog.Info("Model configured", "model", model)
 		if model == nil {
 			large = defaultLarge
 			// override the model type to large