@@ -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 {
@@ -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