[DEPRECATED] Go implementation of plcbundle
1package commands
2
3import (
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
17func NewOpCommand() *cobra.Command {
18 cmd := &cobra.Command{
19 Use: "op",
20 Aliases: []string{"operation", "record"},
21 Short: "Operation queries and inspection",
22 Long: `Operation queries and inspection
23
24Direct access to individual operations within bundles using either:
25 • Bundle number + position (e.g., 42 1337)
26 • Global position (e.g., 420000)
27
28Global position format: (bundleNumber × 10,000) + position
29Example: 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
55func newOpGetCommand() *cobra.Command {
56 var verbose bool
57
58 cmd := &cobra.Command{
59 Use: "get <bundle> <position> | <globalPosition>",
60 Short: "Get operation as JSON",
61 Long: `Get operation as JSON (machine-readable)
62
63Supports two input formats:
64 1. Bundle number + position: get 42 1337
65 2. Global position: get 420000
66
67Global position = (bundleNumber × 10,000) + position
68
69Use -v/--verbose to see detailed timing breakdown.`,
70
71 Example: ` # By bundle + position
72 plcbundle op get 42 1337
73
74 # By global position
75 plcbundle op get 88410345
76
77 # With timing metrics
78 plcbundle op get 42 1337 -v
79 plcbundle op get 88410345 --verbose
80
81 # Pipe to jq
82 plcbundle op get 42 1337 | jq .did`,
83
84 Args: cobra.RangeArgs(1, 2),
85
86 RunE: func(cmd *cobra.Command, args []string) error {
87 bundleNum, position, err := parseOpArgs(args)
88 if err != nil {
89 return err
90 }
91
92 mgr, _, err := getManager(&ManagerOptions{Cmd: cmd})
93 if err != nil {
94 return err
95 }
96 defer mgr.Close()
97
98 ctx := context.Background()
99
100 // Time the operation load
101 totalStart := time.Now()
102 op, err := mgr.LoadOperation(ctx, bundleNum, position)
103 totalDuration := time.Since(totalStart)
104
105 if err != nil {
106 return err
107 }
108
109 if verbose {
110 globalPos := (bundleNum * 10000) + position
111
112 // Log-style output (compact, single-line friendly)
113 fmt.Fprintf(os.Stderr, "[Load] Bundle %06d:%04d (pos=%d) in %s",
114 bundleNum, position, globalPos, totalDuration)
115 fmt.Fprintf(os.Stderr, " | %d bytes", len(op.RawJSON))
116 fmt.Fprintf(os.Stderr, "\n")
117 }
118
119 // Output raw JSON to stdout
120 if len(op.RawJSON) > 0 {
121 fmt.Println(string(op.RawJSON))
122 } else {
123 data, _ := json.Marshal(op)
124 fmt.Println(string(data))
125 }
126
127 return nil
128 },
129 }
130
131 cmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "Show timing metrics")
132
133 return cmd
134}
135
136// // ============================================================================
137// OP SHOW - Show operation (formatted)
138// ============================================================================
139
140func newOpShowCommand() *cobra.Command {
141 var verbose bool
142
143 cmd := &cobra.Command{
144 Use: "show <bundle> <position> | <globalPosition>",
145 Short: "Show operation (human-readable)",
146 Long: `Show operation with formatted output
147
148Displays operation in human-readable format with:
149 • Bundle location and global position
150 • DID and CID
151 • Timestamp and age
152 • Nullification status
153 • Parsed operation details
154 • Performance metrics (with -v)`,
155
156 Example: ` # By bundle + position
157 plcbundle op show 42 1337
158
159 # By global position
160 plcbundle op show 88410345
161
162 # Verbose with timing and full JSON
163 plcbundle op show 42 1337 -v`,
164
165 Args: cobra.RangeArgs(1, 2),
166
167 RunE: func(cmd *cobra.Command, args []string) error {
168 bundleNum, position, err := parseOpArgs(args)
169 if err != nil {
170 return err
171 }
172
173 mgr, _, err := getManager(&ManagerOptions{Cmd: cmd})
174 if err != nil {
175 return err
176 }
177 defer mgr.Close()
178
179 ctx := context.Background()
180
181 // Time the operation
182 loadStart := time.Now()
183 op, err := mgr.LoadOperation(ctx, bundleNum, position)
184 loadDuration := time.Since(loadStart)
185
186 if err != nil {
187 return err
188 }
189
190 // Time the parsing
191 parseStart := time.Now()
192 opData, parseErr := op.GetOperationData()
193 parseDuration := time.Since(parseStart)
194
195 return displayOperationWithTiming(bundleNum, position, op, opData, parseErr,
196 loadDuration, parseDuration, verbose)
197 },
198 }
199
200 cmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "Show timing metrics and full JSON")
201
202 return cmd
203}
204
205// ============================================================================
206// OP FIND - Find operation by CID
207// ============================================================================
208
209func newOpFindCommand() *cobra.Command {
210 cmd := &cobra.Command{
211 Use: "find <cid>",
212 Short: "Find operation by CID",
213 Long: `Find operation by CID across all bundles
214
215Searches the entire repository for an operation with the given CID
216and returns its location (bundle + position).
217
218Note: This performs a full scan and can be slow on large repositories.`,
219
220 Example: ` # Find by CID
221 plcbundle op find bafyreig3tg4k...
222
223 # Pipe to op get
224 plcbundle op find bafyreig3... | awk '{print $3, $5}' | xargs plcbundle op get`,
225
226 Args: cobra.ExactArgs(1),
227
228 RunE: func(cmd *cobra.Command, args []string) error {
229 cid := args[0]
230
231 mgr, _, err := getManager(&ManagerOptions{Cmd: cmd})
232 if err != nil {
233 return err
234 }
235 defer mgr.Close()
236
237 return findOperationByCID(mgr, cid)
238 },
239 }
240
241 return cmd
242}
243
244// ============================================================================
245// Helper Functions
246// ============================================================================
247
248// parseOpArgs parses operation arguments (supports both formats)
249func parseOpArgs(args []string) (bundleNum, position int, err error) {
250 if len(args) == 1 {
251 global, err := strconv.Atoi(args[0])
252 if err != nil {
253 return 0, 0, fmt.Errorf("invalid position: %w", err)
254 }
255
256 if global < types.BUNDLE_SIZE {
257 // Small numbers: shorthand for "bundle 1, position N"
258 // op get 1 → bundle 1, position 1
259 // op get 100 → bundle 1, position 100
260 return 1, global, nil
261 }
262
263 // Large numbers: global position
264 // op get 10000 → bundle 1, position 0
265 // op get 88410345 → bundle 8841, position 345
266 bundleNum = global / types.BUNDLE_SIZE
267 position = global % types.BUNDLE_SIZE
268
269 return bundleNum, position, nil
270 }
271
272 if len(args) == 2 {
273 // Explicit: bundle + position
274 bundleNum, err = strconv.Atoi(args[0])
275 if err != nil {
276 return 0, 0, fmt.Errorf("invalid bundle number: %w", err)
277 }
278
279 position, err = strconv.Atoi(args[1])
280 if err != nil {
281 return 0, 0, fmt.Errorf("invalid position: %w", err)
282 }
283
284 return bundleNum, position, nil
285 }
286
287 return 0, 0, fmt.Errorf("usage: op <command> <bundle> <position> OR op <command> <globalPosition>")
288}
289
290// findOperationByCID searches for an operation by CID
291func findOperationByCID(mgr BundleManager, cid string) error {
292 ctx := context.Background()
293
294 // CHECK MEMPOOL FIRST (most recent data)
295 fmt.Fprintf(os.Stderr, "Checking mempool...\n")
296 mempoolOps, err := mgr.GetMempoolOperations()
297 if err == nil && len(mempoolOps) > 0 {
298 for pos, op := range mempoolOps {
299 if op.CID == cid {
300 fmt.Printf("Found in mempool: position %d\n\n", pos)
301 fmt.Printf(" DID: %s\n", op.DID)
302 fmt.Printf(" Created: %s\n", op.CreatedAt.Format("2006-01-02 15:04:05"))
303
304 if op.IsNullified() {
305 fmt.Printf(" Status: ✗ Nullified")
306 if nullCID := op.GetNullifyingCID(); nullCID != "" {
307 fmt.Printf(" by %s", nullCID)
308 }
309 fmt.Printf("\n")
310 } else {
311 fmt.Printf(" Status: ✓ Active\n")
312 }
313
314 return nil
315 }
316 }
317 }
318
319 // Search bundles
320 index := mgr.GetIndex()
321 bundles := index.GetBundles()
322
323 if len(bundles) == 0 {
324 fmt.Fprintf(os.Stderr, "No bundles to search\n")
325 return nil
326 }
327
328 fmt.Fprintf(os.Stderr, "Searching %d bundles for CID: %s\n\n", len(bundles), cid)
329
330 for _, meta := range bundles {
331 bundle, err := mgr.LoadBundle(ctx, meta.BundleNumber)
332 if err != nil {
333 continue
334 }
335
336 for pos, op := range bundle.Operations {
337 if op.CID == cid {
338 globalPos := (meta.BundleNumber * types.BUNDLE_SIZE) + pos
339
340 fmt.Printf("Found: bundle %06d, position %d\n", meta.BundleNumber, pos)
341 fmt.Printf("Global position: %d\n\n", globalPos)
342
343 fmt.Printf(" DID: %s\n", op.DID)
344 fmt.Printf(" Created: %s\n", op.CreatedAt.Format("2006-01-02 15:04:05"))
345
346 if op.IsNullified() {
347 fmt.Printf(" Status: ✗ Nullified")
348 if nullCID := op.GetNullifyingCID(); nullCID != "" {
349 fmt.Printf(" by %s", nullCID)
350 }
351 fmt.Printf("\n")
352 } else {
353 fmt.Printf(" Status: ✓ Active\n")
354 }
355
356 return nil
357 }
358 }
359
360 // Progress indicator
361 if meta.BundleNumber%100 == 0 {
362 fmt.Fprintf(os.Stderr, "Searched through bundle %06d...\r", meta.BundleNumber)
363 }
364 }
365
366 fmt.Fprintf(os.Stderr, "\nCID not found: %s\n", cid)
367 fmt.Fprintf(os.Stderr, "(Searched %d bundles + mempool)\n", len(bundles))
368 return fmt.Errorf("CID not found")
369}
370
371// displayOperationWithTiming shows formatted operation details with timing
372func displayOperationWithTiming(bundleNum, position int, op *plcclient.PLCOperation,
373 opData map[string]interface{}, _ error,
374 loadDuration, parseDuration time.Duration, verbose bool) error {
375
376 globalPos := (bundleNum * types.BUNDLE_SIZE) + position
377
378 fmt.Printf("═══════════════════════════════════════════════════════════════\n")
379 fmt.Printf(" Operation %d\n", globalPos)
380 fmt.Printf("═══════════════════════════════════════════════════════════════\n\n")
381
382 fmt.Printf("Location\n")
383 fmt.Printf("────────\n")
384 fmt.Printf(" Bundle: %06d\n", bundleNum)
385 fmt.Printf(" Position: %d\n", position)
386 fmt.Printf(" Global position: %d\n\n", globalPos)
387
388 fmt.Printf("Identity\n")
389 fmt.Printf("────────\n")
390 fmt.Printf(" DID: %s\n", op.DID)
391 fmt.Printf(" CID: %s\n\n", op.CID)
392
393 fmt.Printf("Timestamp\n")
394 fmt.Printf("─────────\n")
395 fmt.Printf(" Created: %s\n", op.CreatedAt.Format("2006-01-02 15:04:05.000 MST"))
396 fmt.Printf(" Age: %s\n\n", formatDuration(time.Since(op.CreatedAt)))
397
398 // Status
399 status := "✓ Active"
400 if op.IsNullified() {
401 status = "✗ Nullified"
402 if cid := op.GetNullifyingCID(); cid != "" {
403 status += fmt.Sprintf(" by %s", cid)
404 }
405 }
406 fmt.Printf("Status\n")
407 fmt.Printf("──────\n")
408 fmt.Printf(" %s\n\n", status)
409
410 // Performance metrics (always shown if verbose)
411 if verbose {
412 totalTime := loadDuration + parseDuration
413
414 fmt.Printf("Performance\n")
415 fmt.Printf("───────────\n")
416 fmt.Printf(" Load time: %s\n", loadDuration)
417 fmt.Printf(" Parse time: %s\n", parseDuration)
418 fmt.Printf(" Total time: %s\n", totalTime)
419
420 if len(op.RawJSON) > 0 {
421 fmt.Printf(" Data size: %d bytes\n", len(op.RawJSON))
422 mbPerSec := float64(len(op.RawJSON)) / loadDuration.Seconds() / (1024 * 1024)
423 fmt.Printf(" Load speed: %.2f MB/s\n", mbPerSec)
424 }
425
426 fmt.Printf("\n")
427 }
428
429 // Parse operation details
430 if opData != nil && !op.IsNullified() {
431 fmt.Printf("Details\n")
432 fmt.Printf("───────\n")
433
434 if opType, ok := opData["type"].(string); ok {
435 fmt.Printf(" Type: %s\n", opType)
436 }
437
438 if handle, ok := opData["handle"].(string); ok {
439 fmt.Printf(" Handle: %s\n", handle)
440 } else if aka, ok := opData["alsoKnownAs"].([]interface{}); ok && len(aka) > 0 {
441 if akaStr, ok := aka[0].(string); ok {
442 handle := strings.TrimPrefix(akaStr, "at://")
443 fmt.Printf(" Handle: %s\n", handle)
444 }
445 }
446
447 if services, ok := opData["services"].(map[string]interface{}); ok {
448 if pds, ok := services["atproto_pds"].(map[string]interface{}); ok {
449 if endpoint, ok := pds["endpoint"].(string); ok {
450 fmt.Printf(" PDS: %s\n", endpoint)
451 }
452 }
453 }
454
455 fmt.Printf("\n")
456 }
457
458 // Verbose: show full JSON (pretty-printed)
459 if verbose {
460 fmt.Printf("Raw JSON\n")
461 fmt.Printf("────────\n")
462
463 var data []byte
464 if len(op.RawJSON) > 0 {
465 // Parse and re-format the raw JSON
466 var temp interface{}
467 if err := json.Unmarshal(op.RawJSON, &temp); err == nil {
468 data, _ = json.MarshalIndent(temp, "", " ")
469 } else {
470 // Fallback to raw if parse fails
471 data = op.RawJSON
472 }
473 } else {
474 data, _ = json.MarshalIndent(op, "", " ")
475 }
476
477 fmt.Println(string(data))
478 fmt.Printf("\n")
479 }
480
481 return nil
482}