A Transparent and Verifiable Way to Sync the AT Protocol's PLC Directory

update cmds

+131 -97
+30 -1
bundle/manager.go
··· 61 61 config.Logger = defaultLogger{} 62 62 } 63 63 64 - // Ensure directory exists 64 + // CHECK: Don't auto-create if repository doesn't exist 65 + repoExists := repositoryExists(config.BundleDir) 66 + 67 + if !repoExists && !config.AutoInit { 68 + return nil, fmt.Errorf( 69 + "no plcbundle repository found in: %s\n\n"+ 70 + "Initialize a new repository with:\n"+ 71 + " plcbundle clone <url> # Clone from remote\n"+ 72 + " plcbundle sync # Fetch from PLC directory", 73 + config.BundleDir, 74 + ) 75 + } 76 + 77 + // Ensure directory exists (only if repo exists OR AutoInit is enabled) 65 78 if err := os.MkdirAll(config.BundleDir, 0755); err != nil { 66 79 return nil, fmt.Errorf("failed to create bundle directory: %w", err) 67 80 } ··· 1444 1457 1445 1458 return true, nil 1446 1459 } 1460 + 1461 + // Add this helper function at the top of manager.go 1462 + func repositoryExists(bundleDir string) bool { 1463 + indexPath := filepath.Join(bundleDir, bundleindex.INDEX_FILE) 1464 + 1465 + // Check for index file 1466 + if _, err := os.Stat(indexPath); err == nil { 1467 + return true 1468 + } 1469 + 1470 + // Check for bundle files 1471 + bundleFiles, _ := filepath.Glob(filepath.Join(bundleDir, "*.jsonl.zst")) 1472 + bundleFiles = filterBundleFiles(bundleFiles) 1473 + 1474 + return len(bundleFiles) > 0 1475 + }
+2
bundle/types.go
··· 122 122 BundleDir string 123 123 VerifyOnLoad bool 124 124 AutoRebuild bool 125 + AutoInit bool // Allow auto-creating empty repository 125 126 RebuildWorkers int // Number of workers for parallel rebuild (0 = auto-detect) 126 127 RebuildProgress func(current, total int) // Progress callback for rebuild 127 128 Logger types.Logger ··· 133 134 BundleDir: bundleDir, 134 135 VerifyOnLoad: true, 135 136 AutoRebuild: true, 137 + AutoInit: false, 136 138 RebuildWorkers: 0, // 0 means auto-detect CPU count 137 139 RebuildProgress: nil, // No progress callback by default 138 140 Logger: nil,
+6 -6
cmd/plcbundle/commands/clone.go
··· 99 99 return fmt.Errorf("invalid directory path: %w", err) 100 100 } 101 101 102 - if err := os.MkdirAll(absDir, 0755); err != nil { 103 - return fmt.Errorf("failed to create directory: %w", err) 104 - } 105 - 106 - // ✨ Create manager in target directory 107 - mgr, dir, err := getManagerInDirectory(absDir, "") 102 + // ✨ Clone creates new repository in specific directory 103 + mgr, dir, err := getManager(&ManagerOptions{ 104 + Dir: absDir, 105 + PLCURL: "https://plc.directory", 106 + AutoInit: true, 107 + }) 108 108 if err != nil { 109 109 return err 110 110 }
+53 -45
cmd/plcbundle/commands/common.go
··· 48 48 // PLCOperationWithLocation wraps operation with location info 49 49 type PLCOperationWithLocation = bundle.PLCOperationWithLocation 50 50 51 - // getManager creates or opens a bundle manager 52 - func getManager(plcURL string) (*bundle.Manager, string, error) { 53 - dir, err := os.Getwd() 54 - if err != nil { 55 - return nil, "", err 56 - } 51 + // ============================================================================ 52 + // ✨ MANAGER OPTIONS STRUCT 53 + // ============================================================================ 57 54 58 - if err := os.MkdirAll(dir, 0755); err != nil { 59 - return nil, "", fmt.Errorf("failed to create directory: %w", err) 60 - } 61 - 62 - config := bundle.DefaultConfig(dir) 63 - 64 - var client *plcclient.Client 65 - if plcURL != "" { 66 - client = plcclient.NewClient(plcURL) 67 - } 68 - 69 - mgr, err := bundle.NewManager(config, client) 70 - if err != nil { 71 - return nil, "", err 72 - } 73 - 74 - return mgr, dir, nil 55 + // ManagerOptions configures manager creation 56 + type ManagerOptions struct { 57 + Cmd *cobra.Command // Optional: for reading --dir flag 58 + Dir string // Optional: explicit directory (overrides Cmd flag and cwd) 59 + PLCURL string // Optional: PLC directory URL 60 + AutoInit bool // Optional: allow creating new empty repository (default: false) 75 61 } 76 62 77 - // getManagerInDirectory creates manager in specific directory 78 - func getManagerInDirectory(dir string, plcURL string) (*bundle.Manager, string, error) { 79 - if err := os.MkdirAll(dir, 0755); err != nil { 80 - return nil, "", fmt.Errorf("failed to create directory: %w", err) 81 - } 82 - 83 - config := bundle.DefaultConfig(dir) 84 - 85 - var client *plcclient.Client 86 - if plcURL != "" { 87 - client = plcclient.NewClient(plcURL) 88 - } 63 + // ============================================================================ 64 + // ✨ SINGLE UNIFIED getManager METHOD 65 + // ============================================================================ 89 66 90 - mgr, err := bundle.NewManager(config, client) 91 - if err != nil { 92 - return nil, "", err 67 + // getManager creates or opens a bundle manager 68 + // Pass nil for default options (read-only, current directory, no PLC client) 69 + // 70 + // Examples: 71 + // 72 + // getManager(nil) // All defaults 73 + // getManager(&ManagerOptions{Cmd: cmd}) // Use --dir flag 74 + // getManager(&ManagerOptions{PLCURL: url}) // Add PLC client 75 + // getManager(&ManagerOptions{AutoInit: true}) // Allow creating repo 76 + // getManager(&ManagerOptions{Dir: "/path", AutoInit: true}) // Explicit dir + create 77 + func getManager(opts *ManagerOptions) (*bundle.Manager, string, error) { 78 + // Use defaults if nil 79 + if opts == nil { 80 + opts = &ManagerOptions{} 93 81 } 94 82 95 - return mgr, dir, nil 96 - } 83 + // Determine directory (priority: explicit Dir > Cmd flag > current working dir) 84 + var dir string 97 85 98 - // getManagerFromCommand creates manager using command flags 99 - func getManagerFromCommand(cmd *cobra.Command, plcURL string) (*bundle.Manager, string, error) { 100 - // Get --dir flag from root command 101 - dir, _ := cmd.Root().PersistentFlags().GetString("dir") 86 + if opts.Dir != "" { 87 + // Explicit directory provided 88 + dir = opts.Dir 89 + } else if opts.Cmd != nil { 90 + // Try to get from command --dir flag 91 + dir, _ = opts.Cmd.Root().PersistentFlags().GetString("dir") 92 + } 102 93 94 + // Fallback to current working directory 103 95 if dir == "" { 104 96 var err error 105 97 dir, err = os.Getwd() ··· 114 106 return nil, "", fmt.Errorf("invalid directory path: %w", err) 115 107 } 116 108 117 - return getManagerInDirectory(absDir, plcURL) 109 + // Create config 110 + config := bundle.DefaultConfig(absDir) 111 + config.AutoInit = opts.AutoInit 112 + 113 + // Create PLC client if URL provided 114 + var client *plcclient.Client 115 + if opts.PLCURL != "" { 116 + client = plcclient.NewClient(opts.PLCURL) 117 + } 118 + 119 + // Create manager 120 + mgr, err := bundle.NewManager(config, client) 121 + if err != nil { 122 + return nil, "", err 123 + } 124 + 125 + return mgr, absDir, nil 118 126 } 119 127 120 128 // parseBundleRange parses bundle range string
+2 -2
cmd/plcbundle/commands/detector.go
··· 99 99 return fmt.Errorf("--bundle required") 100 100 } 101 101 102 - mgr, _, err := getManager("") 102 + mgr, _, err := getManager(nil) 103 103 if err != nil { 104 104 return err 105 105 } ··· 195 195 }() 196 196 } 197 197 198 - mgr, _, err := getManager("") 198 + mgr, _, err := getManager(nil) 199 199 if err != nil { 200 200 return err 201 201 }
+5 -5
cmd/plcbundle/commands/did.go
··· 89 89 RunE: func(cmd *cobra.Command, args []string) error { 90 90 did := args[0] 91 91 92 - mgr, _, err := getManagerFromCommand(cmd, "") 92 + mgr, _, err := getManager(&ManagerOptions{Cmd: cmd}) 93 93 if err != nil { 94 94 return err 95 95 } ··· 185 185 RunE: func(cmd *cobra.Command, args []string) error { 186 186 did := args[0] 187 187 188 - mgr, _, err := getManagerFromCommand(cmd, "") 188 + mgr, _, err := getManager(&ManagerOptions{Cmd: cmd}) 189 189 if err != nil { 190 190 return err 191 191 } ··· 274 274 RunE: func(cmd *cobra.Command, args []string) error { 275 275 did := args[0] 276 276 277 - mgr, _, err := getManagerFromCommand(cmd, "") 277 + mgr, _, err := getManager(&ManagerOptions{Cmd: cmd}) 278 278 if err != nil { 279 279 return err 280 280 } ··· 383 383 " cat dids.txt | plcbundle did batch -") 384 384 } 385 385 386 - mgr, _, err := getManagerFromCommand(cmd, "") 386 + mgr, _, err := getManager(&ManagerOptions{Cmd: cmd}) 387 387 if err != nil { 388 388 return err 389 389 } ··· 439 439 Args: cobra.MaximumNArgs(1), 440 440 441 441 RunE: func(cmd *cobra.Command, args []string) error { 442 - mgr, dir, err := getManagerFromCommand(cmd, "") 442 + mgr, dir, err := getManager(&ManagerOptions{Cmd: cmd}) 443 443 if err != nil { 444 444 return err 445 445 }
+1 -1
cmd/plcbundle/commands/diff.go
··· 67 67 RunE: func(cmd *cobra.Command, args []string) error { 68 68 target := args[0] 69 69 70 - mgr, dir, err := getManagerFromCommand(cmd, "") 70 + mgr, dir, err := getManager(&ManagerOptions{Cmd: cmd}) 71 71 if err != nil { 72 72 return err 73 73 }
+1 -1
cmd/plcbundle/commands/export.go
··· 34 34 " plcbundle export --bundles 42 | jq .") 35 35 } 36 36 37 - mgr, _, err := getManager("") 37 + mgr, _, err := getManager(&ManagerOptions{Cmd: nil}) 38 38 if err != nil { 39 39 return err 40 40 }
+1 -1
cmd/plcbundle/commands/getop.go
··· 25 25 return fmt.Errorf("invalid position") 26 26 } 27 27 28 - mgr, _, err := getManager("") 28 + mgr, _, err := getManager(nil) 29 29 if err != nil { 30 30 return err 31 31 }
+4 -4
cmd/plcbundle/commands/index.go
··· 63 63 return err 64 64 } 65 65 66 - mgr, dir, err := getManager("") 66 + mgr, dir, err := getManager(nil) 67 67 if err != nil { 68 68 return err 69 69 } ··· 115 115 } 116 116 117 117 func indexStats(args []string) error { 118 - mgr, dir, err := getManager("") 118 + mgr, dir, err := getManager(nil) 119 119 if err != nil { 120 120 return err 121 121 } ··· 184 184 185 185 did := fs.Arg(0) 186 186 187 - mgr, _, err := getManager("") 187 + mgr, _, err := getManager(nil) 188 188 if err != nil { 189 189 return err 190 190 } ··· 255 255 256 256 did := fs.Arg(0) 257 257 258 - mgr, _, err := getManager("") 258 + mgr, _, err := getManager(nil) 259 259 if err != nil { 260 260 return err 261 261 }
+1 -1
cmd/plcbundle/commands/log.go
··· 68 68 plcbundle history -n 5`, 69 69 70 70 RunE: func(cmd *cobra.Command, args []string) error { 71 - mgr, dir, err := getManagerFromCommand(cmd, "") 71 + mgr, dir, err := getManager(&ManagerOptions{Cmd: cmd}) 72 72 if err != nil { 73 73 return err 74 74 }
+1 -1
cmd/plcbundle/commands/ls.go
··· 52 52 plcbundle ls --separator "," > bundles.csv # Export to CSV`, 53 53 54 54 RunE: func(cmd *cobra.Command, args []string) error { 55 - mgr, _, err := getManagerFromCommand(cmd, "") 55 + mgr, _, err := getManager(&ManagerOptions{Cmd: cmd}) 56 56 if err != nil { 57 57 return err 58 58 }
+3 -3
cmd/plcbundle/commands/mempool.go
··· 92 92 } 93 93 } 94 94 95 - mgr, dir, err := getManagerFromCommand(cmd, "") 95 + mgr, dir, err := getManager(&ManagerOptions{Cmd: cmd}) 96 96 if err != nil { 97 97 return err 98 98 } ··· 130 130 plcbundle mempool clear -f`, 131 131 132 132 RunE: func(cmd *cobra.Command, args []string) error { 133 - mgr, dir, err := getManagerFromCommand(cmd, "") 133 + mgr, dir, err := getManager(&ManagerOptions{Cmd: cmd}) 134 134 if err != nil { 135 135 return err 136 136 } ··· 204 204 plcbundle mempool export`, 205 205 206 206 RunE: func(cmd *cobra.Command, args []string) error { 207 - mgr, _, err := getManagerFromCommand(cmd, "") 207 + mgr, _, err := getManager(&ManagerOptions{Cmd: cmd}) 208 208 if err != nil { 209 209 return err 210 210 }
+1 -1
cmd/plcbundle/commands/rollback.go
··· 63 63 RunE: func(cmd *cobra.Command, args []string) error { 64 64 verbose, _ := cmd.Root().PersistentFlags().GetBool("verbose") 65 65 66 - mgr, dir, err := getManagerFromCommand(cmd, "") 66 + mgr, dir, err := getManager(&ManagerOptions{Cmd: cmd}) 67 67 if err != nil { 68 68 return err 69 69 }
+6 -11
cmd/plcbundle/commands/server.go
··· 76 76 RunE: func(cmd *cobra.Command, args []string) error { 77 77 verbose, _ := cmd.Root().PersistentFlags().GetBool("verbose") 78 78 79 - // Get manager from command (uses --dir flag) 80 - var mgr *bundle.Manager 81 - var dir string 82 - var err error 83 - 84 - if syncMode { 85 - mgr, dir, err = getManagerFromCommand(cmd, plcURL) 86 - } else { 87 - mgr, dir, err = getManagerFromCommand(cmd, "") 88 - } 89 - 79 + // ✨ Server in sync mode can create repo, read-only mode cannot 80 + mgr, dir, err := getManager(&ManagerOptions{ 81 + Cmd: cmd, 82 + PLCURL: plcURL, 83 + AutoInit: syncMode, // Only auto-init if syncing 84 + }) 90 85 if err != nil { 91 86 return err 92 87 }
+1 -1
cmd/plcbundle/commands/status.go
··· 27 27 plcbundle info`, 28 28 29 29 RunE: func(cmd *cobra.Command, args []string) error { 30 - mgr, dir, err := getManagerFromCommand(cmd, "") 30 + mgr, dir, err := getManager(&ManagerOptions{Cmd: cmd}) 31 31 if err != nil { 32 32 return err 33 33 }
+1 -7
cmd/plcbundle/commands/stream.go
··· 56 56 verbose, _ := cmd.Root().PersistentFlags().GetBool("verbose") 57 57 quiet, _ := cmd.Root().PersistentFlags().GetBool("quiet") 58 58 59 - // Setup PLC client only if syncing 60 - var effectivePLCURL string 61 - if sync { 62 - effectivePLCURL = plcURL 63 - } 64 - 65 - mgr, dir, err := getManagerFromCommand(cmd, effectivePLCURL) 59 + mgr, dir, err := getManager(&ManagerOptions{Cmd: cmd, PLCURL: plcURL}) 66 60 if err != nil { 67 61 return err 68 62 }
+6 -2
cmd/plcbundle/commands/sync.go
··· 57 57 verbose, _ := cmd.Root().PersistentFlags().GetBool("verbose") 58 58 quiet, _ := cmd.Root().PersistentFlags().GetBool("quiet") 59 59 60 - // Get manager using global --dir flag 61 - mgr, dir, err := getManagerFromCommand(cmd, plcURL) 60 + // ✨ Sync creates repository if missing 61 + mgr, dir, err := getManager(&ManagerOptions{ 62 + Cmd: cmd, 63 + PLCURL: plcURL, 64 + AutoInit: true, 65 + }) 62 66 if err != nil { 63 67 return err 64 68 }
+1 -1
cmd/plcbundle/commands/verify.go
··· 55 55 RunE: func(cmd *cobra.Command, args []string) error { 56 56 verbose, _ := cmd.Root().PersistentFlags().GetBool("verbose") 57 57 58 - mgr, dir, err := getManagerFromCommand(cmd, "") 58 + mgr, dir, err := getManager(&ManagerOptions{Cmd: cmd}) 59 59 if err != nil { 60 60 return err 61 61 }
+5 -3
internal/didindex/manager.go
··· 21 21 shardDir := filepath.Join(indexDir, DID_INDEX_SHARDS) 22 22 configPath := filepath.Join(indexDir, DID_INDEX_CONFIG) 23 23 24 - // Ensure directories exist 25 - os.MkdirAll(shardDir, 0755) 26 - 27 24 // Load or create config 28 25 config, _ := loadIndexConfig(configPath) 29 26 if config == nil { ··· 46 43 config: config, 47 44 logger: logger, 48 45 } 46 + } 47 + 48 + // Add helper to ensure directories when actually writing 49 + func (dim *Manager) ensureDirectories() error { 50 + return os.MkdirAll(dim.shardDir, 0755) 49 51 } 50 52 51 53 // Close unmaps all shards and cleans up