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.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344package main
import ( "fmt" "os"
"github.com/cpfiffer/note/internal/api" "github.com/cpfiffer/note/internal/sync" "github.com/cpfiffer/note/internal/web" "github.com/spf13/cobra")
var ( forceFlag bool dirFlag string nameFlag string portFlag string)
func main() { rootCmd := &cobra.Command{ Use: "note-sync", Short: "Sync Letta notes with local markdown files", Long: `A CLI tool to bidirectionally sync Letta memory blocks with a local folder of markdown files.`, }
// Init command initCmd := &cobra.Command{ Use: "init <agent_id>", Short: "Initialize a sync directory for an agent", Args: cobra.ExactArgs(1), RunE: runInit, } initCmd.Flags().StringVar(&dirFlag, "dir", ".", "Directory to initialize") rootCmd.AddCommand(initCmd)
// Pull command pullCmd := &cobra.Command{ Use: "pull", Short: "Download notes from Letta to local files", RunE: runPull, } pullCmd.Flags().BoolVar(&forceFlag, "force", false, "Overwrite local changes without conflict check") rootCmd.AddCommand(pullCmd)
// Push command pushCmd := &cobra.Command{ Use: "push", Short: "Upload local files to Letta", RunE: runPush, } pushCmd.Flags().BoolVar(&forceFlag, "force", false, "Overwrite remote changes without conflict check") rootCmd.AddCommand(pushCmd)
// Status command statusCmd := &cobra.Command{ Use: "status", Short: "Show sync status", RunE: runStatus, } rootCmd.AddCommand(statusCmd)
// Agents command agentsCmd := &cobra.Command{ Use: "agents", Short: "List available agents", RunE: runAgents, } agentsCmd.Flags().StringVar(&nameFlag, "name", "", "Search agents by name") rootCmd.AddCommand(agentsCmd)
// Serve command (web UI) serveCmd := &cobra.Command{ Use: "serve", Short: "Start web UI for browsing and editing notes", RunE: runServe, } serveCmd.Flags().StringVar(&portFlag, "port", "8080", "Port to listen on") rootCmd.AddCommand(serveCmd)
if err := rootCmd.Execute(); err != nil { os.Exit(1) }}
func runInit(cmd *cobra.Command, args []string) error { agentID := args[0]
// Check if already initialized if _, err := sync.LoadState(dirFlag); err == nil { return fmt.Errorf("directory already initialized. Delete %s to reinitialize", sync.StateFileName) }
// Verify API key works client, err := api.NewClient("") if err != nil { return err }
// Try to list blocks to verify agent exists ownerSearch := fmt.Sprintf("owner:%s", agentID) _, err = client.ListBlocks(ownerSearch) if err != nil { return fmt.Errorf("failed to connect to Letta API: %w", err) }
state, err := sync.InitState(dirFlag, agentID, "") if err != nil { return err }
fmt.Printf("Initialized note-sync for agent %s\n", state.AgentID) fmt.Printf("State file: %s/%s\n", dirFlag, sync.StateFileName) fmt.Println("\nNext steps:") fmt.Println(" note-sync pull # Download notes from Letta") fmt.Println(" note-sync push # Upload local changes") fmt.Println(" note-sync status # Check sync status")
return nil}
func runPull(cmd *cobra.Command, args []string) error { dir, err := os.Getwd() if err != nil { return err }
state, err := sync.LoadState(dir) if err != nil { return err }
client, err := api.NewClient(state.LettaBaseURL) if err != nil { return err }
opts := sync.PullOptions{ Force: forceFlag, Dir: dir, }
result, err := sync.Pull(client, state, opts) if err != nil { return err }
// Save updated state if err := state.SaveState(dir); err != nil { return fmt.Errorf("failed to save state: %w", err) }
// Print results if len(result.Created) > 0 { fmt.Println("Created:") for _, p := range result.Created { fmt.Printf(" + %s\n", p) } } if len(result.Updated) > 0 { fmt.Println("Updated:") for _, p := range result.Updated { fmt.Printf(" ~ %s\n", p) } } if len(result.Conflicts) > 0 { fmt.Println("Conflicts (see .conflict.md files):") for _, p := range result.Conflicts { fmt.Printf(" ! %s\n", p) } } if len(result.Created) == 0 && len(result.Updated) == 0 && len(result.Conflicts) == 0 { fmt.Println("Already up to date.") }
return nil}
func runPush(cmd *cobra.Command, args []string) error { dir, err := os.Getwd() if err != nil { return err }
state, err := sync.LoadState(dir) if err != nil { return err }
client, err := api.NewClient(state.LettaBaseURL) if err != nil { return err }
opts := sync.PushOptions{ Force: forceFlag, Dir: dir, }
result, err := sync.Push(client, state, opts) if err != nil { return err }
// Save updated state if err := state.SaveState(dir); err != nil { return fmt.Errorf("failed to save state: %w", err) }
// Print results if len(result.Created) > 0 { fmt.Println("Created:") for _, p := range result.Created { fmt.Printf(" + %s\n", p) } } if len(result.Updated) > 0 { fmt.Println("Updated:") for _, p := range result.Updated { fmt.Printf(" ~ %s\n", p) } } if len(result.Conflicts) > 0 { fmt.Println("Conflicts (pull first to see remote changes):") for _, p := range result.Conflicts { fmt.Printf(" ! %s\n", p) } } if len(result.Created) == 0 && len(result.Updated) == 0 && len(result.Conflicts) == 0 { fmt.Println("Nothing to push.") }
return nil}
func runStatus(cmd *cobra.Command, args []string) error { dir, err := os.Getwd() if err != nil { return err }
state, err := sync.LoadState(dir) if err != nil { return err }
client, err := api.NewClient(state.LettaBaseURL) if err != nil { return err }
result, err := sync.Status(client, state, dir) if err != nil { return err }
hasChanges := false
if len(result.ModifiedLocally) > 0 { hasChanges = true fmt.Println("Modified locally (push to upload):") for _, p := range result.ModifiedLocally { fmt.Printf(" M %s\n", p) } }
if len(result.ModifiedRemotely) > 0 { hasChanges = true fmt.Println("Modified remotely (pull to download):") for _, p := range result.ModifiedRemotely { fmt.Printf(" M %s\n", p) } }
if len(result.Conflicts) > 0 { hasChanges = true fmt.Println("Conflicts (both changed):") for _, p := range result.Conflicts { fmt.Printf(" ! %s\n", p) } }
if len(result.UntrackedLocal) > 0 { hasChanges = true fmt.Println("Untracked local (push to create):") for _, p := range result.UntrackedLocal { fmt.Printf(" ? %s\n", p) } }
if len(result.UntrackedRemote) > 0 { hasChanges = true fmt.Println("Untracked remote (pull to download):") for _, p := range result.UntrackedRemote { fmt.Printf(" ? %s\n", p) } }
if !hasChanges { fmt.Printf("Everything in sync. (%d files)\n", len(result.Synced)) }
return nil}
func runAgents(cmd *cobra.Command, args []string) error { client, err := api.NewClient("") if err != nil { return err }
agents, err := client.ListAgents(nameFlag) if err != nil { return fmt.Errorf("failed to list agents: %w", err) }
if len(agents) == 0 { if nameFlag != "" { fmt.Printf("No agents found matching name '%s'\n", nameFlag) } else { fmt.Println("No agents found") } return nil }
fmt.Printf("%-40s %s\n", "ID", "NAME") fmt.Printf("%-40s %s\n", "----", "----") for _, agent := range agents { fmt.Printf("%-40s %s\n", agent.ID, agent.Name) }
return nil}
func runServe(cmd *cobra.Command, args []string) error { server, err := web.NewServer() if err != nil { return err }
addr := "localhost:" + portFlag return server.Start(addr)}