claude-web-chat/files.go
djuka 0ce03c27e3
All checks were successful
Tests / unit-tests (push) Successful in 8s
Terminal UI stil, markdown tabele i prikaz troškova
- Dodat goldmark sa Table/Strikethrough/TaskList ekstenzijama (markdown.go)
- Prepisana CSS tema na konzolni stil (JetBrains Mono, tamna pozadina, prompt prefix)
- Prikaz troškova i trajanja posle svakog Claude odgovora (duration, cost, turns)
- Ispravljen parsing result eventa (json.RawMessage + top-level polja)
- Ispravljen concurrent write bug na WebSocket (write channel pattern)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 05:38:47 +00:00

104 lines
2.2 KiB
Go

package main
import (
"fmt"
"os"
"path/filepath"
"sort"
"strings"
)
type FileInfo struct {
Name string
Path string
RelPath string
IsDir bool
Size int64
}
// ListMarkdownFiles returns all .md files in the project directory (non-recursive).
func ListMarkdownFiles(projectDir string) ([]FileInfo, error) {
entries, err := os.ReadDir(projectDir)
if err != nil {
return nil, err
}
var files []FileInfo
for _, e := range entries {
if e.IsDir() {
continue
}
if !strings.HasSuffix(strings.ToLower(e.Name()), ".md") {
continue
}
info, err := e.Info()
if err != nil {
continue
}
files = append(files, FileInfo{
Name: e.Name(),
Path: filepath.Join(projectDir, e.Name()),
RelPath: e.Name(),
Size: info.Size(),
})
}
// Also check docs/ subdirectory
docsDir := filepath.Join(projectDir, "docs")
if docEntries, err := os.ReadDir(docsDir); err == nil {
for _, e := range docEntries {
if e.IsDir() {
continue
}
if !strings.HasSuffix(strings.ToLower(e.Name()), ".md") {
continue
}
info, err := e.Info()
if err != nil {
continue
}
files = append(files, FileInfo{
Name: "docs/" + e.Name(),
Path: filepath.Join(docsDir, e.Name()),
RelPath: "docs/" + e.Name(),
Size: info.Size(),
})
}
}
sort.Slice(files, func(i, j int) bool {
return files[i].Name < files[j].Name
})
return files, nil
}
// ReadFileContent reads a file and returns its content.
// It validates that the file is within the project directory (path traversal protection).
func ReadFileContent(projectDir, relPath string) (string, error) {
absPath := filepath.Join(projectDir, relPath)
absPath = filepath.Clean(absPath)
// Path traversal protection
if !strings.HasPrefix(absPath, filepath.Clean(projectDir)) {
return "", fmt.Errorf("path traversal detected")
}
data, err := os.ReadFile(absPath)
if err != nil {
return "", err
}
return string(data), nil
}
// RenderMarkdownFile reads a markdown file and returns rendered HTML.
func RenderMarkdownFile(projectDir, relPath string) (string, error) {
content, err := ReadFileContent(projectDir, relPath)
if err != nil {
return "", err
}
return RenderMD(content), nil
}