Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 39 additions & 5 deletions cmd/entire/cli/agent/claudecode/claude.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,14 @@ import (
"os"
"path/filepath"
"regexp"
"strings"
"time"

"github.com/entireio/cli/cmd/entire/cli/agent"
"github.com/entireio/cli/cmd/entire/cli/agent/types"
"github.com/entireio/cli/cmd/entire/cli/paths"
"github.com/entireio/cli/cmd/entire/cli/platform"
"github.com/entireio/cli/cmd/entire/cli/stringutil"
"github.com/entireio/cli/cmd/entire/cli/transcript"
)

Expand Down Expand Up @@ -95,13 +98,29 @@ func (c *ClaudeCodeAgent) GetSessionDir(repoPath string) (string, error) {
return override, nil
}

homeDir, err := os.UserHomeDir()
if err != nil {
return "", fmt.Errorf("failed to get home directory: %w", err)
homeDirs := platform.HomeDirCandidates()
if len(homeDirs) == 0 {
// fallback
home, err := os.UserHomeDir()
if err != nil {
return "", fmt.Errorf("failed to get home directory: %w", err)
}
homeDirs = []string{home}
}

projectDirs := claudeProjectDirCandidates(repoPath)
// Try to find an existing directory first (important on WSL due to path-shape differences).
for _, home := range homeDirs {
for _, proj := range projectDirs {
p := filepath.Join(home, ".claude", "projects", proj)
if agent.DirExists(p) {
return p, nil
}
}
}

projectDir := SanitizePathForClaude(repoPath)
return filepath.Join(homeDir, ".claude", "projects", projectDir), nil
// Default to the first candidate.
return filepath.Join(homeDirs[0], ".claude", "projects", projectDirs[0]), nil
}

// ReadSession reads a session from Claude's storage (JSONL transcript file).
Expand Down Expand Up @@ -357,3 +376,18 @@ func (c *ClaudeCodeAgent) ChunkTranscript(_ context.Context, content []byte, max
func (c *ClaudeCodeAgent) ReassembleTranscript(chunks [][]byte) ([]byte, error) {
return agent.ReassembleJSONL(chunks), nil
}

func claudeProjectDirCandidates(repoPath string) []string {
cands := []string{SanitizePathForClaude(repoPath)}
if strings.HasPrefix(repoPath, "/") {
// Claude's sanitize keeps leading "/" (becomes leading "-").
// Some environments may store without it, so try both.
cands = append(cands, SanitizePathForClaude(strings.TrimLeft(repoPath, "/")))
}
if platform.IsWSL() {
for _, win := range platform.WindowsPathCandidatesFromWSLPath(repoPath) {
cands = append(cands, SanitizePathForClaude(win))
}
}
return stringutil.UniqueStrings(cands)
}
35 changes: 30 additions & 5 deletions cmd/entire/cli/agent/cursor/cursor.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import (
"github.com/entireio/cli/cmd/entire/cli/agent"
"github.com/entireio/cli/cmd/entire/cli/agent/types"
"github.com/entireio/cli/cmd/entire/cli/paths"
"github.com/entireio/cli/cmd/entire/cli/platform"
"github.com/entireio/cli/cmd/entire/cli/stringutil"
)

//nolint:gochecknoinits // Agent self-registration is the intended pattern
Expand Down Expand Up @@ -94,13 +96,26 @@ func (c *CursorAgent) GetSessionDir(repoPath string) (string, error) {
return override, nil
}

homeDir, err := os.UserHomeDir()
if err != nil {
return "", fmt.Errorf("failed to get home directory: %w", err)
homeDirs := platform.HomeDirCandidates()
if len(homeDirs) == 0 {
home, err := os.UserHomeDir()
if err != nil {
return "", fmt.Errorf("failed to get home directory: %w", err)
}
homeDirs = []string{home}
}

projectDirs := cursorProjectDirCandidates(repoPath)
for _, home := range homeDirs {
for _, proj := range projectDirs {
p := filepath.Join(home, ".cursor", "projects", proj, "agent-transcripts")
if agent.DirExists(p) {
return p, nil
}
}
}

projectDir := sanitizePathForCursor(repoPath)
return filepath.Join(homeDir, ".cursor", "projects", projectDir, "agent-transcripts"), nil
return filepath.Join(homeDirs[0], ".cursor", "projects", projectDirs[0], "agent-transcripts"), nil
}

// ReadSession reads a session from Cursor's storage (JSONL transcript file).
Expand Down Expand Up @@ -178,3 +193,13 @@ func (c *CursorAgent) ChunkTranscript(_ context.Context, content []byte, maxSize
func (c *CursorAgent) ReassembleTranscript(chunks [][]byte) ([]byte, error) {
return agent.ReassembleJSONL(chunks), nil
}

func cursorProjectDirCandidates(repoPath string) []string {
cands := []string{sanitizePathForCursor(repoPath)}
if platform.IsWSL() {
for _, win := range platform.WindowsPathCandidatesFromWSLPath(repoPath) {
cands = append(cands, sanitizePathForCursor(win))
}
}
return stringutil.UniqueStrings(cands)
}
9 changes: 9 additions & 0 deletions cmd/entire/cli/agent/fsutil.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package agent

import "os"

// DirExists reports whether the named directory exists.
func DirExists(path string) bool {
info, err := os.Stat(path)
return err == nil && info.IsDir()
}
36 changes: 31 additions & 5 deletions cmd/entire/cli/paths/paths.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import (
"sync"

"github.com/entireio/cli/cmd/entire/cli/checkpoint/id"
"github.com/entireio/cli/cmd/entire/cli/platform"
"github.com/entireio/cli/cmd/entire/cli/stringutil"
)

// Directory constants
Expand Down Expand Up @@ -156,13 +158,37 @@ func GetClaudeProjectDir(repoPath string) (string, error) {
return override, nil
}

homeDir, err := os.UserHomeDir()
if err != nil {
return "", fmt.Errorf("failed to get home directory: %w", err)
homeDirs := platform.HomeDirCandidates()
if len(homeDirs) == 0 {
home, err := os.UserHomeDir()
if err != nil {
return "", fmt.Errorf("failed to get home directory: %w", err)
}
homeDirs = []string{home}
}

// Candidates matter on WSL because Windows-native Claude uses Windows path strings.
projectDirs := []string{SanitizePathForClaude(repoPath)}
if strings.HasPrefix(repoPath, "/") {
projectDirs = append(projectDirs, SanitizePathForClaude(strings.TrimLeft(repoPath, "/")))
}
if platform.IsWSL() {
for _, win := range platform.WindowsPathCandidatesFromWSLPath(repoPath) {
projectDirs = append(projectDirs, SanitizePathForClaude(win))
}
}
projectDirs = stringutil.UniqueStrings(projectDirs)

for _, home := range homeDirs {
for _, proj := range projectDirs {
p := filepath.Join(home, ".claude", "projects", proj)
if info, err := os.Stat(p); err == nil && info.IsDir() {
return p, nil
}
}
}

projectDir := SanitizePathForClaude(repoPath)
return filepath.Join(homeDir, ".claude", "projects", projectDir), nil
return filepath.Join(homeDirs[0], ".claude", "projects", projectDirs[0]), nil
}

// SessionMetadataDirFromSessionID returns the path to a session's metadata directory
Expand Down
Loading