package claude import ( "bufio" "encoding/json" "os" "time" ) func TailHistoryFile(stop <-chan struct{}, hub *EventHub, path string) { var offset int64 for { select { case <-stop: return default: } stat, err := os.Stat(path) if err != nil { if !os.IsNotExist(err) { hub.Publish(Event{ TS: time.Now(), Type: EventTypeServerError, Data: map[string]any{"error": err.Error()}, }) } time.Sleep(1 * time.Second) continue } size := stat.Size() if size > offset { if err := processNewBytes(path, offset, size, hub); err != nil { hub.Publish(Event{ TS: time.Now(), Type: EventTypeServerError, Data: map[string]any{"error": err.Error()}, }) } offset = size } else if size < offset { offset = 0 hub.Publish(Event{ TS: time.Now(), Type: EventTypeServerNotice, Data: map[string]any{"msg": "file truncated, resetting offset"}, }) } time.Sleep(500 * time.Millisecond) } } func processNewBytes(path string, oldSize, newSize int64, hub *EventHub) error { f, err := os.Open(path) if err != nil { return err } defer f.Close() if _, err := f.Seek(oldSize, 0); err != nil { return err } scanner := bufio.NewScanner(f) for scanner.Scan() { line := scanner.Text() if line == "" { continue } data := map[string]any{ "rawLine": line, } var jsonData map[string]any if err := json.Unmarshal([]byte(line), &jsonData); err != nil { data["parseError"] = err.Error() } else { data["json"] = jsonData summary := map[string]string{} if v, ok := jsonData["sessionId"].(string); ok { summary["sessionId"] = v } if v, ok := jsonData["project"].(string); ok { summary["project"] = v } if v, ok := jsonData["display"].(string); ok { summary["display"] = v } data["summary"] = summary } hub.Publish(Event{ TS: time.Now(), Type: EventTypeHistoryAppend, Data: data, }) } return scanner.Err() }