Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/just-cameron/note. External storage tool for Letta agents to manage persistent notes
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132package sync
import ( "fmt" "os" "path/filepath" "regexp" "strings" "time"
"github.com/cpfiffer/note/internal/api" "github.com/cpfiffer/note/internal/util")
// PullOptions configures the pull operationtype PullOptions struct { Force bool Dir string}
// PullResult contains the results of a pull operationtype PullResult struct { Created []string Updated []string Conflicts []string Unchanged []string Skipped []string}
// Pull downloads notes from Letta to local filesfunc Pull(client *api.Client, state *State, opts PullOptions) (*PullResult, error) { result := &PullResult{}
// Pattern to filter out legacy UUID paths and note_directory uuidPattern := regexp.MustCompile(`/\[?agent-[a-f0-9-]+\]?/`)
// Fetch all blocks for this agent ownerSearch := fmt.Sprintf("owner:%s", state.AgentID) blocks, err := client.ListBlocks(ownerSearch) if err != nil { return nil, fmt.Errorf("failed to list blocks: %w", err) }
for _, block := range blocks { // Skip non-path labels if !strings.HasPrefix(block.Label, "/") { continue }
// Skip note_directory (auto-generated) if block.Label == "/note_directory" { result.Skipped = append(result.Skipped, block.Label) continue }
// Skip legacy UUID paths if uuidPattern.MatchString(block.Label) { result.Skipped = append(result.Skipped, block.Label) continue }
localPath := filepath.Join(opts.Dir, PathToFile(block.Label)) remoteHash := util.HashContent(block.Value)
// Check if file exists locally localContent, err := os.ReadFile(localPath) localExists := err == nil
if localExists { localHash := util.HashContent(string(localContent)) fileState, hasState := state.Files[block.Label]
if hasState && localHash != fileState.LocalHash && remoteHash != fileState.RemoteHash { // Both changed - conflict! if !opts.Force { // Write conflict file with remote content conflictPath := filepath.Join(opts.Dir, ConflictFileName(block.Label)) if err := writeFile(conflictPath, block.Value); err != nil { return nil, fmt.Errorf("failed to write conflict file: %w", err) } result.Conflicts = append(result.Conflicts, block.Label) continue } }
if remoteHash == localHash { // No changes result.Unchanged = append(result.Unchanged, block.Label) // Update state in case it was missing state.Files[block.Label] = FileState{ LocalHash: localHash, RemoteHash: remoteHash, SyncedAt: time.Now(), BlockID: block.ID, } continue }
// Remote changed (or force mode) if err := writeFile(localPath, block.Value); err != nil { return nil, fmt.Errorf("failed to write file %s: %w", localPath, err) } result.Updated = append(result.Updated, block.Label) } else { // New file if err := writeFile(localPath, block.Value); err != nil { return nil, fmt.Errorf("failed to write file %s: %w", localPath, err) } result.Created = append(result.Created, block.Label) }
// Update state state.Files[block.Label] = FileState{ LocalHash: remoteHash, // Local now matches remote RemoteHash: remoteHash, SyncedAt: time.Now(), BlockID: block.ID, } }
return result, nil}
// writeFile writes content to a file, creating directories as neededfunc writeFile(path, content string) error { dir := filepath.Dir(path) if err := os.MkdirAll(dir, 0755); err != nil { return fmt.Errorf("failed to create directory: %w", err) } return os.WriteFile(path, []byte(content), 0644)}