[DEPRECATED] Go implementation of plcbundle

cmd op

+386 -52
-49
cmd/plcbundle/commands/getop.go
··· 1 - package commands 2 - 3 - import ( 4 - "context" 5 - "fmt" 6 - "strconv" 7 - 8 - "github.com/goccy/go-json" 9 - ) 10 - 11 - // GetOpCommand handles the get-op subcommand 12 - func GetOpCommand(args []string) error { 13 - if len(args) < 2 { 14 - return fmt.Errorf("usage: plcbundle get-op <bundle> <position>\n" + 15 - "Example: plcbundle get-op 42 1337") 16 - } 17 - 18 - bundleNum, err := strconv.Atoi(args[0]) 19 - if err != nil { 20 - return fmt.Errorf("invalid bundle number") 21 - } 22 - 23 - position, err := strconv.Atoi(args[1]) 24 - if err != nil { 25 - return fmt.Errorf("invalid position") 26 - } 27 - 28 - mgr, _, err := getManager(nil) 29 - if err != nil { 30 - return err 31 - } 32 - defer mgr.Close() 33 - 34 - ctx := context.Background() 35 - op, err := mgr.LoadOperation(ctx, bundleNum, position) 36 - if err != nil { 37 - return err 38 - } 39 - 40 - // Output JSON 41 - if len(op.RawJSON) > 0 { 42 - fmt.Println(string(op.RawJSON)) 43 - } else { 44 - data, _ := json.Marshal(op) 45 - fmt.Println(string(data)) 46 - } 47 - 48 - return nil 49 - }
+384
cmd/plcbundle/commands/op.go
··· 1 + package commands 2 + 3 + import ( 4 + "context" 5 + "fmt" 6 + "os" 7 + "strconv" 8 + "strings" 9 + "time" 10 + 11 + "github.com/goccy/go-json" 12 + "github.com/spf13/cobra" 13 + "tangled.org/atscan.net/plcbundle/internal/plcclient" 14 + "tangled.org/atscan.net/plcbundle/internal/types" 15 + ) 16 + 17 + func NewOpCommand() *cobra.Command { 18 + cmd := &cobra.Command{ 19 + Use: "op", 20 + Aliases: []string{"operation"}, 21 + Short: "Operation queries and inspection", 22 + Long: `Operation queries and inspection 23 + 24 + Direct access to individual operations within bundles using either: 25 + • Bundle number + position (e.g., 42 1337) 26 + • Global position (e.g., 420000) 27 + 28 + Global position format: (bundleNumber × 10,000) + position 29 + Example: 88410345 = bundle 8841, position 345`, 30 + 31 + Example: ` # Get operation as JSON 32 + plcbundle op get 42 1337 33 + plcbundle op get 420000 34 + 35 + # Show operation (formatted) 36 + plcbundle op show 42 1337 37 + plcbundle op show 88410345 38 + 39 + # Find by CID 40 + plcbundle op find bafyreig3...`, 41 + } 42 + 43 + // Add subcommands 44 + cmd.AddCommand(newOpGetCommand()) 45 + cmd.AddCommand(newOpShowCommand()) 46 + cmd.AddCommand(newOpFindCommand()) 47 + 48 + return cmd 49 + } 50 + 51 + // ============================================================================ 52 + // OP GET - Get operation as JSON 53 + // ============================================================================ 54 + 55 + func newOpGetCommand() *cobra.Command { 56 + cmd := &cobra.Command{ 57 + Use: "get <bundle> <position> | <globalPosition>", 58 + Short: "Get operation as JSON", 59 + Long: `Get operation as JSON (machine-readable) 60 + 61 + Supports two input formats: 62 + 1. Bundle number + position: get 42 1337 63 + 2. Global position: get 420000 64 + 65 + Global position = (bundleNumber × 10,000) + position`, 66 + 67 + Example: ` # By bundle + position 68 + plcbundle op get 42 1337 69 + 70 + # By global position 71 + plcbundle op get 88410345 72 + 73 + # Pipe to jq 74 + plcbundle op get 42 1337 | jq .did`, 75 + 76 + Args: cobra.RangeArgs(1, 2), 77 + 78 + RunE: func(cmd *cobra.Command, args []string) error { 79 + bundleNum, position, err := parseOpArgs(args) 80 + if err != nil { 81 + return err 82 + } 83 + 84 + mgr, _, err := getManager(&ManagerOptions{Cmd: cmd}) 85 + if err != nil { 86 + return err 87 + } 88 + defer mgr.Close() 89 + 90 + ctx := context.Background() 91 + op, err := mgr.LoadOperation(ctx, bundleNum, position) 92 + if err != nil { 93 + return err 94 + } 95 + 96 + // Output raw JSON 97 + if len(op.RawJSON) > 0 { 98 + fmt.Println(string(op.RawJSON)) 99 + } else { 100 + data, _ := json.Marshal(op) 101 + fmt.Println(string(data)) 102 + } 103 + 104 + return nil 105 + }, 106 + } 107 + 108 + return cmd 109 + } 110 + 111 + // ============================================================================ 112 + // OP SHOW - Show operation (formatted) 113 + // ============================================================================ 114 + 115 + func newOpShowCommand() *cobra.Command { 116 + var verbose bool 117 + 118 + cmd := &cobra.Command{ 119 + Use: "show <bundle> <position> | <globalPosition>", 120 + Short: "Show operation (human-readable)", 121 + Long: `Show operation with formatted output 122 + 123 + Displays operation in human-readable format with: 124 + • Bundle location and global position 125 + • DID and CID 126 + • Timestamp and age 127 + • Nullification status 128 + • Parsed operation details`, 129 + 130 + Example: ` # By bundle + position 131 + plcbundle op show 42 1337 132 + 133 + # By global position 134 + plcbundle op show 88410345 135 + 136 + # Verbose (show full operation JSON) 137 + plcbundle op show 42 1337 -v`, 138 + 139 + Args: cobra.RangeArgs(1, 2), 140 + 141 + RunE: func(cmd *cobra.Command, args []string) error { 142 + bundleNum, position, err := parseOpArgs(args) 143 + if err != nil { 144 + return err 145 + } 146 + 147 + mgr, _, err := getManager(&ManagerOptions{Cmd: cmd}) 148 + if err != nil { 149 + return err 150 + } 151 + defer mgr.Close() 152 + 153 + ctx := context.Background() 154 + op, err := mgr.LoadOperation(ctx, bundleNum, position) 155 + if err != nil { 156 + return err 157 + } 158 + 159 + return displayOperation(bundleNum, position, op, verbose) 160 + }, 161 + } 162 + 163 + cmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "Show full operation JSON") 164 + 165 + return cmd 166 + } 167 + 168 + // ============================================================================ 169 + // OP FIND - Find operation by CID 170 + // ============================================================================ 171 + 172 + func newOpFindCommand() *cobra.Command { 173 + cmd := &cobra.Command{ 174 + Use: "find <cid>", 175 + Short: "Find operation by CID", 176 + Long: `Find operation by CID across all bundles 177 + 178 + Searches the entire repository for an operation with the given CID 179 + and returns its location (bundle + position). 180 + 181 + Note: This performs a full scan and can be slow on large repositories.`, 182 + 183 + Example: ` # Find by CID 184 + plcbundle op find bafyreig3tg4k... 185 + 186 + # Pipe to op get 187 + plcbundle op find bafyreig3... | awk '{print $3, $5}' | xargs plcbundle op get`, 188 + 189 + Args: cobra.ExactArgs(1), 190 + 191 + RunE: func(cmd *cobra.Command, args []string) error { 192 + cid := args[0] 193 + 194 + mgr, _, err := getManager(&ManagerOptions{Cmd: cmd}) 195 + if err != nil { 196 + return err 197 + } 198 + defer mgr.Close() 199 + 200 + return findOperationByCID(mgr, cid) 201 + }, 202 + } 203 + 204 + return cmd 205 + } 206 + 207 + // ============================================================================ 208 + // Helper Functions 209 + // ============================================================================ 210 + 211 + // parseOpArgs parses operation arguments (supports both formats) 212 + func parseOpArgs(args []string) (bundleNum, position int, err error) { 213 + if len(args) == 1 { 214 + global, err := strconv.Atoi(args[0]) 215 + if err != nil { 216 + return 0, 0, fmt.Errorf("invalid position: %w", err) 217 + } 218 + 219 + if global < types.BUNDLE_SIZE { 220 + // Small numbers: shorthand for "bundle 1, position N" 221 + // op get 1 → bundle 1, position 1 222 + // op get 100 → bundle 1, position 100 223 + return 1, global, nil 224 + } 225 + 226 + // Large numbers: global position 227 + // op get 10000 → bundle 1, position 0 228 + // op get 88410345 → bundle 8841, position 345 229 + bundleNum = global / types.BUNDLE_SIZE 230 + position = global % types.BUNDLE_SIZE 231 + 232 + return bundleNum, position, nil 233 + } 234 + 235 + if len(args) == 2 { 236 + // Explicit: bundle + position 237 + bundleNum, err = strconv.Atoi(args[0]) 238 + if err != nil { 239 + return 0, 0, fmt.Errorf("invalid bundle number: %w", err) 240 + } 241 + 242 + position, err = strconv.Atoi(args[1]) 243 + if err != nil { 244 + return 0, 0, fmt.Errorf("invalid position: %w", err) 245 + } 246 + 247 + return bundleNum, position, nil 248 + } 249 + 250 + return 0, 0, fmt.Errorf("usage: op <command> <bundle> <position> OR op <command> <globalPosition>") 251 + } 252 + 253 + // displayOperation shows formatted operation details 254 + func displayOperation(bundleNum, position int, op *plcclient.PLCOperation, verbose bool) error { 255 + globalPos := (bundleNum * types.BUNDLE_SIZE) + position 256 + 257 + fmt.Printf("Operation %d\n", globalPos) 258 + fmt.Printf("═══════════════════════════════════════════════════════════════\n\n") 259 + 260 + fmt.Printf("Location\n") 261 + fmt.Printf("────────\n") 262 + fmt.Printf(" Bundle: %06d\n", bundleNum) 263 + fmt.Printf(" Position: %d\n", position) 264 + fmt.Printf(" Global position: %d\n\n", globalPos) 265 + 266 + fmt.Printf("Identity\n") 267 + fmt.Printf("────────\n") 268 + fmt.Printf(" DID: %s\n", op.DID) 269 + fmt.Printf(" CID: %s\n\n", op.CID) 270 + 271 + fmt.Printf("Timestamp\n") 272 + fmt.Printf("─────────\n") 273 + fmt.Printf(" Created: %s\n", op.CreatedAt.Format("2006-01-02 15:04:05.000 MST")) 274 + fmt.Printf(" Age: %s\n\n", formatDuration(time.Since(op.CreatedAt))) 275 + 276 + // Status 277 + status := "✓ Active" 278 + if op.IsNullified() { 279 + status = "✗ Nullified" 280 + if cid := op.GetNullifyingCID(); cid != "" { 281 + status += fmt.Sprintf(" by %s", cid) 282 + } 283 + } 284 + fmt.Printf("Status\n") 285 + fmt.Printf("──────\n") 286 + fmt.Printf(" %s\n\n", status) 287 + 288 + // Parse operation details 289 + if opData, err := op.GetOperationData(); err == nil && opData != nil && !op.IsNullified() { 290 + fmt.Printf("Details\n") 291 + fmt.Printf("───────\n") 292 + 293 + if opType, ok := opData["type"].(string); ok { 294 + fmt.Printf(" Type: %s\n", opType) 295 + } 296 + 297 + if handle, ok := opData["handle"].(string); ok { 298 + fmt.Printf(" Handle: %s\n", handle) 299 + } else if aka, ok := opData["alsoKnownAs"].([]interface{}); ok && len(aka) > 0 { 300 + if akaStr, ok := aka[0].(string); ok { 301 + handle := strings.TrimPrefix(akaStr, "at://") 302 + fmt.Printf(" Handle: %s\n", handle) 303 + } 304 + } 305 + 306 + if services, ok := opData["services"].(map[string]interface{}); ok { 307 + if pds, ok := services["atproto_pds"].(map[string]interface{}); ok { 308 + if endpoint, ok := pds["endpoint"].(string); ok { 309 + fmt.Printf(" PDS: %s\n", endpoint) 310 + } 311 + } 312 + } 313 + 314 + fmt.Printf("\n") 315 + } 316 + 317 + // Verbose: show full JSON 318 + if verbose { 319 + fmt.Printf("Raw JSON\n") 320 + fmt.Printf("────────\n") 321 + if len(op.RawJSON) > 0 { 322 + fmt.Println(string(op.RawJSON)) 323 + } else { 324 + data, _ := json.MarshalIndent(op, "", " ") 325 + fmt.Println(string(data)) 326 + } 327 + fmt.Printf("\n") 328 + } 329 + 330 + return nil 331 + } 332 + 333 + // findOperationByCID searches for an operation by CID 334 + func findOperationByCID(mgr BundleManager, cid string) error { 335 + ctx := context.Background() 336 + index := mgr.GetIndex() 337 + bundles := index.GetBundles() 338 + 339 + if len(bundles) == 0 { 340 + fmt.Fprintf(os.Stderr, "No bundles to search\n") 341 + return nil 342 + } 343 + 344 + fmt.Fprintf(os.Stderr, "Searching %d bundles for CID: %s\n\n", len(bundles), cid) 345 + 346 + for _, meta := range bundles { 347 + bundle, err := mgr.LoadBundle(ctx, meta.BundleNumber) 348 + if err != nil { 349 + continue 350 + } 351 + 352 + for pos, op := range bundle.Operations { 353 + if op.CID == cid { 354 + globalPos := (meta.BundleNumber * types.BUNDLE_SIZE) + pos 355 + 356 + fmt.Printf("Found: bundle %06d, position %d\n", meta.BundleNumber, pos) 357 + fmt.Printf("Global position: %d\n\n", globalPos) 358 + 359 + fmt.Printf(" DID: %s\n", op.DID) 360 + fmt.Printf(" Created: %s\n", op.CreatedAt.Format("2006-01-02 15:04:05")) 361 + 362 + if op.IsNullified() { 363 + fmt.Printf(" Status: ✗ Nullified") 364 + if nullCID := op.GetNullifyingCID(); nullCID != "" { 365 + fmt.Printf(" by %s", nullCID) 366 + } 367 + fmt.Printf("\n") 368 + } else { 369 + fmt.Printf(" Status: ✓ Active\n") 370 + } 371 + 372 + return nil 373 + } 374 + } 375 + 376 + // Progress indicator 377 + if meta.BundleNumber%100 == 0 { 378 + fmt.Fprintf(os.Stderr, "Searched through bundle %06d...\r", meta.BundleNumber) 379 + } 380 + } 381 + 382 + fmt.Fprintf(os.Stderr, "\nCID not found: %s\n", cid) 383 + return fmt.Errorf("CID not found") 384 + }
+2 -3
cmd/plcbundle/main.go
··· 46 46 // Bundle operations (root level - most common) 47 47 cmd.AddCommand(commands.NewSyncCommand()) 48 48 cmd.AddCommand(commands.NewCloneCommand()) 49 - /*cmd.AddCommand(commands.NewPullCommand()) 50 - cmd.AddCommand(commands.NewExportCommand())*/ 49 + //cmd.AddCommand(commands.NewExportCommand()) 51 50 cmd.AddCommand(commands.NewStreamCommand()) 52 - //cmd.AddCommand(commands.NewGetCommand()) 53 51 cmd.AddCommand(commands.NewRollbackCommand()) 54 52 55 53 // Status & info (root level) 56 54 cmd.AddCommand(commands.NewStatusCommand()) 57 55 cmd.AddCommand(commands.NewLogCommand()) 58 56 cmd.AddCommand(commands.NewLsCommand()) 57 + cmd.AddCommand(commands.NewOpCommand()) 59 58 //cmd.AddCommand(commands.NewGapsCommand()) 60 59 cmd.AddCommand(commands.NewVerifyCommand()) 61 60 cmd.AddCommand(commands.NewDiffCommand())