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