package claude import ( "encoding/json" "fmt" "os" "path/filepath" "time" ) // Loader reads Claude Code state files from a local claude directory (typically ~/.claude). // // Keep this minimal for now; more helpers (e.g. ListDir / FileInfo) can be added later. type Loader struct { claudeDir string } type DirEntry struct { Name string `json:"name"` IsDir bool `json:"isDir"` } type FileMeta struct { Path string `json:"path"` Exists bool `json:"exists"` Size int64 `json:"size"` ModTime string `json:"modTime"` } func NewLoader(claudeDir string) *Loader { return &Loader{claudeDir: claudeDir} } func (l *Loader) ClaudeDir() string { return l.claudeDir } func (l *Loader) LoadStatsCache() (*StatsCache, error) { if l.claudeDir == "" { return nil, fmt.Errorf("claude dir is empty") } p := filepath.Join(l.claudeDir, "stats-cache.json") b, err := os.ReadFile(p) if err != nil { return nil, fmt.Errorf("read stats cache %q: %w", p, err) } var stats StatsCache if err := json.Unmarshal(b, &stats); err != nil { return nil, fmt.Errorf("parse stats cache %q: %w", p, err) } return &stats, nil } func (l *Loader) ListDir(name string) ([]DirEntry, error) { if l.claudeDir == "" { return nil, fmt.Errorf("claude dir is empty") } entries, err := os.ReadDir(filepath.Join(l.claudeDir, name)) if err != nil { return nil, fmt.Errorf("read dir %q: %w", name, err) } out := make([]DirEntry, 0, len(entries)) for _, e := range entries { out = append(out, DirEntry{Name: e.Name(), IsDir: e.IsDir()}) } return out, nil } func (l *Loader) PathExists(relPath string) bool { if l.claudeDir == "" { return false } _, err := os.Stat(filepath.Join(l.claudeDir, relPath)) return err == nil } func (l *Loader) FileMeta(relPath string) (FileMeta, error) { if l.claudeDir == "" { return FileMeta{}, fmt.Errorf("claude dir is empty") } p := filepath.Join(l.claudeDir, relPath) st, err := os.Stat(p) if err != nil { if os.IsNotExist(err) { return FileMeta{Path: relPath, Exists: false}, nil } return FileMeta{}, fmt.Errorf("stat %q: %w", p, err) } return FileMeta{ Path: relPath, Exists: true, Size: st.Size(), ModTime: st.ModTime().UTC().Format(time.RFC3339), }, nil }