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
status cmd
tree.fail
4 months ago
9ada429c
aa2909ec
+232
-411
3 changed files
expand all
collapse all
unified
split
cmd
plcbundle
commands
info.go
status.go
main.go
-409
cmd/plcbundle/commands/info.go
···
1
1
-
package commands
2
2
-
3
3
-
import (
4
4
-
"context"
5
5
-
"fmt"
6
6
-
"path/filepath"
7
7
-
"sort"
8
8
-
"strings"
9
9
-
"time"
10
10
-
11
11
-
flag "github.com/spf13/pflag"
12
12
-
13
13
-
"tangled.org/atscan.net/plcbundle/bundle"
14
14
-
"tangled.org/atscan.net/plcbundle/internal/bundleindex"
15
15
-
"tangled.org/atscan.net/plcbundle/internal/types"
16
16
-
)
17
17
-
18
18
-
// InfoCommand handles the info subcommand
19
19
-
func InfoCommand(args []string) error {
20
20
-
fs := flag.NewFlagSet("info", flag.ExitOnError)
21
21
-
bundleNum := fs.Int("bundle", 0, "specific bundle info (0 = general info)")
22
22
-
verbose := fs.Bool("v", false, "verbose output")
23
23
-
showBundles := fs.Bool("bundles", false, "show bundle list")
24
24
-
verify := fs.Bool("verify", false, "verify chain integrity")
25
25
-
showTimeline := fs.Bool("timeline", false, "show timeline visualization")
26
26
-
27
27
-
if err := fs.Parse(args); err != nil {
28
28
-
return err
29
29
-
}
30
30
-
31
31
-
mgr, dir, err := getManager("")
32
32
-
if err != nil {
33
33
-
return err
34
34
-
}
35
35
-
defer mgr.Close()
36
36
-
37
37
-
if *bundleNum > 0 {
38
38
-
return showBundleInfo(mgr, dir, *bundleNum, *verbose)
39
39
-
}
40
40
-
41
41
-
return showGeneralInfo(mgr, dir, *verbose, *showBundles, *verify, *showTimeline)
42
42
-
}
43
43
-
44
44
-
func showGeneralInfo(mgr *bundle.Manager, dir string, verbose, showBundles, verify, showTimeline bool) error {
45
45
-
index := mgr.GetIndex()
46
46
-
info := mgr.GetInfo()
47
47
-
stats := index.GetStats()
48
48
-
bundleCount := stats["bundle_count"].(int)
49
49
-
50
50
-
fmt.Printf("\n═══════════════════════════════════════════════════════════════\n")
51
51
-
fmt.Printf(" PLC Bundle Repository Overview\n")
52
52
-
fmt.Printf("═══════════════════════════════════════════════════════════════\n\n")
53
53
-
54
54
-
fmt.Printf("📁 Location\n")
55
55
-
fmt.Printf(" Directory: %s\n", dir)
56
56
-
fmt.Printf(" Index: %s\n\n", filepath.Base(info["index_path"].(string)))
57
57
-
58
58
-
fmt.Printf("🌐 Origin\n")
59
59
-
fmt.Printf(" Source: %s\n\n", index.Origin)
60
60
-
61
61
-
if bundleCount == 0 {
62
62
-
fmt.Printf("⚠️ No bundles found\n\n")
63
63
-
fmt.Printf("Get started:\n")
64
64
-
fmt.Printf(" plcbundle fetch # Fetch bundles from PLC\n")
65
65
-
fmt.Printf(" plcbundle rebuild # Rebuild index from existing files\n\n")
66
66
-
return nil
67
67
-
}
68
68
-
69
69
-
firstBundle := stats["first_bundle"].(int)
70
70
-
lastBundle := stats["last_bundle"].(int)
71
71
-
totalCompressedSize := stats["total_size"].(int64)
72
72
-
totalUncompressedSize := stats["total_uncompressed_size"].(int64)
73
73
-
startTime := stats["start_time"].(time.Time)
74
74
-
endTime := stats["end_time"].(time.Time)
75
75
-
updatedAt := stats["updated_at"].(time.Time)
76
76
-
77
77
-
// Summary
78
78
-
fmt.Printf("📊 Summary\n")
79
79
-
fmt.Printf(" Bundles: %s\n", formatNumber(bundleCount))
80
80
-
fmt.Printf(" Range: %06d → %06d\n", firstBundle, lastBundle)
81
81
-
fmt.Printf(" Compressed: %s\n", formatBytes(totalCompressedSize))
82
82
-
fmt.Printf(" Uncompressed: %s\n", formatBytes(totalUncompressedSize))
83
83
-
if totalUncompressedSize > 0 {
84
84
-
ratio := float64(totalUncompressedSize) / float64(totalCompressedSize)
85
85
-
fmt.Printf(" Ratio: %.2fx compression\n", ratio)
86
86
-
}
87
87
-
fmt.Printf(" Avg/Bundle: %s\n\n", formatBytes(totalCompressedSize/int64(bundleCount)))
88
88
-
89
89
-
// Timeline
90
90
-
duration := endTime.Sub(startTime)
91
91
-
fmt.Printf("📅 Timeline\n")
92
92
-
fmt.Printf(" First Op: %s\n", startTime.Format("2006-01-02 15:04:05 MST"))
93
93
-
fmt.Printf(" Last Op: %s\n", endTime.Format("2006-01-02 15:04:05 MST"))
94
94
-
fmt.Printf(" Timespan: %s\n", formatDuration(duration))
95
95
-
fmt.Printf(" Last Updated: %s\n", updatedAt.Format("2006-01-02 15:04:05 MST"))
96
96
-
fmt.Printf(" Age: %s ago\n\n", formatDuration(time.Since(updatedAt)))
97
97
-
98
98
-
// Operations
99
99
-
mempoolStats := mgr.GetMempoolStats()
100
100
-
mempoolCount := mempoolStats["count"].(int)
101
101
-
bundleOpsCount := bundleCount * types.BUNDLE_SIZE
102
102
-
totalOps := bundleOpsCount + mempoolCount
103
103
-
104
104
-
fmt.Printf("🔢 Operations\n")
105
105
-
fmt.Printf(" Bundles: %s records\n", formatNumber(bundleOpsCount))
106
106
-
if mempoolCount > 0 {
107
107
-
fmt.Printf(" Mempool: %s records\n", formatNumber(mempoolCount))
108
108
-
}
109
109
-
fmt.Printf(" Total: %s records\n", formatNumber(totalOps))
110
110
-
if duration.Hours() > 0 {
111
111
-
opsPerHour := float64(bundleOpsCount) / duration.Hours()
112
112
-
fmt.Printf(" Rate: %.0f ops/hour (bundles)\n", opsPerHour)
113
113
-
}
114
114
-
fmt.Printf("\n")
115
115
-
116
116
-
// Hashes
117
117
-
firstMeta, err := index.GetBundle(firstBundle)
118
118
-
if err == nil {
119
119
-
fmt.Printf("🔐 Chain Hashes\n")
120
120
-
fmt.Printf(" Root (bundle %06d):\n", firstBundle)
121
121
-
fmt.Printf(" %s\n", firstMeta.Hash)
122
122
-
123
123
-
lastMeta, err := index.GetBundle(lastBundle)
124
124
-
if err == nil {
125
125
-
fmt.Printf(" Head (bundle %06d):\n", lastBundle)
126
126
-
fmt.Printf(" %s\n", lastMeta.Hash)
127
127
-
}
128
128
-
fmt.Printf("\n")
129
129
-
}
130
130
-
131
131
-
// Gaps
132
132
-
gaps := index.FindGaps()
133
133
-
if len(gaps) > 0 {
134
134
-
fmt.Printf("⚠️ Missing Bundles: %d\n", len(gaps))
135
135
-
if len(gaps) <= 10 || verbose {
136
136
-
fmt.Printf(" ")
137
137
-
for i, gap := range gaps {
138
138
-
if i > 0 {
139
139
-
fmt.Printf(", ")
140
140
-
}
141
141
-
if i > 0 && i%10 == 0 {
142
142
-
fmt.Printf("\n ")
143
143
-
}
144
144
-
fmt.Printf("%06d", gap)
145
145
-
}
146
146
-
fmt.Printf("\n")
147
147
-
} else {
148
148
-
fmt.Printf(" First few: ")
149
149
-
for i := 0; i < 5; i++ {
150
150
-
if i > 0 {
151
151
-
fmt.Printf(", ")
152
152
-
}
153
153
-
fmt.Printf("%06d", gaps[i])
154
154
-
}
155
155
-
fmt.Printf(" ... (use -v to show all)\n")
156
156
-
}
157
157
-
fmt.Printf("\n")
158
158
-
}
159
159
-
160
160
-
// Mempool
161
161
-
if mempoolCount > 0 {
162
162
-
targetBundle := mempoolStats["target_bundle"].(int)
163
163
-
canCreate := mempoolStats["can_create_bundle"].(bool)
164
164
-
progress := float64(mempoolCount) / float64(types.BUNDLE_SIZE) * 100
165
165
-
166
166
-
fmt.Printf("🔄 Mempool (next bundle: %06d)\n", targetBundle)
167
167
-
fmt.Printf(" Operations: %s / %s\n", formatNumber(mempoolCount), formatNumber(types.BUNDLE_SIZE))
168
168
-
fmt.Printf(" Progress: %.1f%%\n", progress)
169
169
-
170
170
-
barWidth := 40
171
171
-
filled := int(float64(barWidth) * float64(mempoolCount) / float64(types.BUNDLE_SIZE))
172
172
-
if filled > barWidth {
173
173
-
filled = barWidth
174
174
-
}
175
175
-
bar := strings.Repeat("█", filled) + strings.Repeat("░", barWidth-filled)
176
176
-
fmt.Printf(" [%s]\n", bar)
177
177
-
178
178
-
if canCreate {
179
179
-
fmt.Printf(" ✓ Ready to create bundle\n")
180
180
-
} else {
181
181
-
remaining := types.BUNDLE_SIZE - mempoolCount
182
182
-
fmt.Printf(" Need %s more operations\n", formatNumber(remaining))
183
183
-
}
184
184
-
fmt.Printf("\n")
185
185
-
}
186
186
-
187
187
-
// Chain verification
188
188
-
if verify {
189
189
-
fmt.Printf("🔐 Chain Verification\n")
190
190
-
fmt.Printf(" Verifying %d bundles...\n", bundleCount)
191
191
-
192
192
-
ctx := context.Background()
193
193
-
result, err := mgr.VerifyChain(ctx)
194
194
-
if err != nil {
195
195
-
fmt.Printf(" ✗ Verification failed: %v\n", err)
196
196
-
} else if result.Valid {
197
197
-
fmt.Printf(" ✓ Chain is valid\n")
198
198
-
fmt.Printf(" ✓ All %d bundles verified\n", len(result.VerifiedBundles))
199
199
-
200
200
-
lastMeta, _ := index.GetBundle(lastBundle)
201
201
-
if lastMeta != nil {
202
202
-
fmt.Printf(" Head: %s\n", lastMeta.Hash)
203
203
-
}
204
204
-
} else {
205
205
-
fmt.Printf(" ✗ Chain is broken\n")
206
206
-
fmt.Printf(" Verified: %d/%d bundles\n", len(result.VerifiedBundles), bundleCount)
207
207
-
fmt.Printf(" Broken at: %06d\n", result.BrokenAt)
208
208
-
fmt.Printf(" Error: %s\n", result.Error)
209
209
-
}
210
210
-
fmt.Printf("\n")
211
211
-
}
212
212
-
213
213
-
// Timeline
214
214
-
if showTimeline {
215
215
-
fmt.Printf("📈 Timeline Visualization\n")
216
216
-
visualizeTimeline(index, verbose)
217
217
-
fmt.Printf("\n")
218
218
-
}
219
219
-
220
220
-
// Bundle list
221
221
-
if showBundles {
222
222
-
bundles := index.GetBundles()
223
223
-
fmt.Printf("📚 Bundle List (%d total)\n\n", len(bundles))
224
224
-
fmt.Printf(" Number | Start Time | End Time | Ops | DIDs | Size\n")
225
225
-
fmt.Printf(" ---------|---------------------|---------------------|--------|--------|--------\n")
226
226
-
227
227
-
for _, meta := range bundles {
228
228
-
fmt.Printf(" %06d | %s | %s | %6d | %6d | %7s\n",
229
229
-
meta.BundleNumber,
230
230
-
meta.StartTime.Format("2006-01-02 15:04"),
231
231
-
meta.EndTime.Format("2006-01-02 15:04"),
232
232
-
meta.OperationCount,
233
233
-
meta.DIDCount,
234
234
-
formatBytes(meta.CompressedSize))
235
235
-
}
236
236
-
fmt.Printf("\n")
237
237
-
} else if bundleCount > 0 {
238
238
-
fmt.Printf("💡 Tip: Use --bundles to see detailed bundle list\n")
239
239
-
fmt.Printf(" Use --timeline to see timeline visualization\n")
240
240
-
fmt.Printf(" Use --verify to verify chain integrity\n\n")
241
241
-
}
242
242
-
243
243
-
return nil
244
244
-
}
245
245
-
246
246
-
func showBundleInfo(mgr *bundle.Manager, dir string, bundleNum int, verbose bool) error {
247
247
-
ctx := context.Background()
248
248
-
b, err := mgr.LoadBundle(ctx, bundleNum)
249
249
-
if err != nil {
250
250
-
return err
251
251
-
}
252
252
-
253
253
-
fmt.Printf("\n═══════════════════════════════════════════════════════════════\n")
254
254
-
fmt.Printf(" Bundle %06d\n", b.BundleNumber)
255
255
-
fmt.Printf("═══════════════════════════════════════════════════════════════\n\n")
256
256
-
257
257
-
fmt.Printf("📁 Location\n")
258
258
-
fmt.Printf(" Directory: %s\n", dir)
259
259
-
fmt.Printf(" File: %06d.jsonl.zst\n\n", b.BundleNumber)
260
260
-
261
261
-
duration := b.EndTime.Sub(b.StartTime)
262
262
-
fmt.Printf("📅 Time Range\n")
263
263
-
fmt.Printf(" Start: %s\n", b.StartTime.Format("2006-01-02 15:04:05.000 MST"))
264
264
-
fmt.Printf(" End: %s\n", b.EndTime.Format("2006-01-02 15:04:05.000 MST"))
265
265
-
fmt.Printf(" Duration: %s\n", formatDuration(duration))
266
266
-
fmt.Printf(" Created: %s\n\n", b.CreatedAt.Format("2006-01-02 15:04:05 MST"))
267
267
-
268
268
-
fmt.Printf("📊 Content\n")
269
269
-
fmt.Printf(" Operations: %s\n", formatNumber(len(b.Operations)))
270
270
-
fmt.Printf(" Unique DIDs: %s\n", formatNumber(b.DIDCount))
271
271
-
if len(b.Operations) > 0 && b.DIDCount > 0 {
272
272
-
avgOpsPerDID := float64(len(b.Operations)) / float64(b.DIDCount)
273
273
-
fmt.Printf(" Avg ops/DID: %.2f\n", avgOpsPerDID)
274
274
-
}
275
275
-
fmt.Printf("\n")
276
276
-
277
277
-
fmt.Printf("💾 Size\n")
278
278
-
fmt.Printf(" Compressed: %s\n", formatBytes(b.CompressedSize))
279
279
-
fmt.Printf(" Uncompressed: %s\n", formatBytes(b.UncompressedSize))
280
280
-
fmt.Printf(" Ratio: %.2fx\n", b.CompressionRatio())
281
281
-
fmt.Printf(" Efficiency: %.1f%% savings\n\n", (1-float64(b.CompressedSize)/float64(b.UncompressedSize))*100)
282
282
-
283
283
-
fmt.Printf("🔐 Cryptographic Hashes\n")
284
284
-
fmt.Printf(" Chain Hash:\n %s\n", b.Hash)
285
285
-
fmt.Printf(" Content Hash:\n %s\n", b.ContentHash)
286
286
-
fmt.Printf(" Compressed:\n %s\n", b.CompressedHash)
287
287
-
if b.Parent != "" {
288
288
-
fmt.Printf(" Parent Chain Hash:\n %s\n", b.Parent)
289
289
-
}
290
290
-
fmt.Printf("\n")
291
291
-
292
292
-
if verbose && len(b.Operations) > 0 {
293
293
-
showBundleSamples(b)
294
294
-
showBundleDIDStats(b)
295
295
-
}
296
296
-
297
297
-
return nil
298
298
-
}
299
299
-
300
300
-
func showBundleSamples(b *bundle.Bundle) {
301
301
-
fmt.Printf("📝 Sample Operations (first 5)\n")
302
302
-
showCount := 5
303
303
-
if len(b.Operations) < showCount {
304
304
-
showCount = len(b.Operations)
305
305
-
}
306
306
-
307
307
-
for i := 0; i < showCount; i++ {
308
308
-
op := b.Operations[i]
309
309
-
fmt.Printf(" %d. %s\n", i+1, op.DID)
310
310
-
fmt.Printf(" CID: %s\n", op.CID)
311
311
-
fmt.Printf(" Time: %s\n", op.CreatedAt.Format("2006-01-02 15:04:05.000"))
312
312
-
if op.IsNullified() {
313
313
-
fmt.Printf(" ⚠️ Nullified: %s\n", op.GetNullifyingCID())
314
314
-
}
315
315
-
}
316
316
-
fmt.Printf("\n")
317
317
-
}
318
318
-
319
319
-
func showBundleDIDStats(b *bundle.Bundle) {
320
320
-
didOps := make(map[string]int)
321
321
-
for _, op := range b.Operations {
322
322
-
didOps[op.DID]++
323
323
-
}
324
324
-
325
325
-
type didCount struct {
326
326
-
did string
327
327
-
count int
328
328
-
}
329
329
-
330
330
-
var counts []didCount
331
331
-
for did, count := range didOps {
332
332
-
counts = append(counts, didCount{did, count})
333
333
-
}
334
334
-
335
335
-
sort.Slice(counts, func(i, j int) bool {
336
336
-
return counts[i].count > counts[j].count
337
337
-
})
338
338
-
339
339
-
fmt.Printf("🏆 Most Active DIDs\n")
340
340
-
showCount := 5
341
341
-
if len(counts) < showCount {
342
342
-
showCount = len(counts)
343
343
-
}
344
344
-
345
345
-
for i := 0; i < showCount; i++ {
346
346
-
fmt.Printf(" %d. %s (%d ops)\n", i+1, counts[i].did, counts[i].count)
347
347
-
}
348
348
-
fmt.Printf("\n")
349
349
-
}
350
350
-
351
351
-
func visualizeTimeline(index *bundleindex.Index, verbose bool) {
352
352
-
bundles := index.GetBundles()
353
353
-
if len(bundles) == 0 {
354
354
-
return
355
355
-
}
356
356
-
357
357
-
type dateGroup struct {
358
358
-
date string
359
359
-
count int
360
360
-
first int
361
361
-
last int
362
362
-
}
363
363
-
364
364
-
dateMap := make(map[string]*dateGroup)
365
365
-
for _, meta := range bundles {
366
366
-
dateStr := meta.StartTime.Format("2006-01-02")
367
367
-
if group, exists := dateMap[dateStr]; exists {
368
368
-
group.count++
369
369
-
group.last = meta.BundleNumber
370
370
-
} else {
371
371
-
dateMap[dateStr] = &dateGroup{
372
372
-
date: dateStr,
373
373
-
count: 1,
374
374
-
first: meta.BundleNumber,
375
375
-
last: meta.BundleNumber,
376
376
-
}
377
377
-
}
378
378
-
}
379
379
-
380
380
-
var dates []string
381
381
-
for date := range dateMap {
382
382
-
dates = append(dates, date)
383
383
-
}
384
384
-
sort.Strings(dates)
385
385
-
386
386
-
maxCount := 0
387
387
-
for _, group := range dateMap {
388
388
-
if group.count > maxCount {
389
389
-
maxCount = group.count
390
390
-
}
391
391
-
}
392
392
-
393
393
-
fmt.Printf("\n")
394
394
-
barWidth := 40
395
395
-
for _, date := range dates {
396
396
-
group := dateMap[date]
397
397
-
barLen := int(float64(barWidth) * float64(group.count) / float64(maxCount))
398
398
-
if barLen == 0 && group.count > 0 {
399
399
-
barLen = 1
400
400
-
}
401
401
-
402
402
-
bar := strings.Repeat("█", barLen)
403
403
-
fmt.Printf(" %s | %-40s | %3d bundles", group.date, bar, group.count)
404
404
-
if verbose {
405
405
-
fmt.Printf(" (%06d-%06d)", group.first, group.last)
406
406
-
}
407
407
-
fmt.Printf("\n")
408
408
-
}
409
409
-
}
+230
cmd/plcbundle/commands/status.go
···
1
1
+
// repo/cmd/plcbundle/commands/status.go
2
2
+
package commands
3
3
+
4
4
+
import (
5
5
+
"fmt"
6
6
+
"strings"
7
7
+
"time"
8
8
+
9
9
+
"github.com/spf13/cobra"
10
10
+
"tangled.org/atscan.net/plcbundle/bundle"
11
11
+
"tangled.org/atscan.net/plcbundle/internal/types"
12
12
+
)
13
13
+
14
14
+
func NewStatusCommand() *cobra.Command {
15
15
+
cmd := &cobra.Command{
16
16
+
Use: "status",
17
17
+
Aliases: []string{"info"},
18
18
+
Short: "Show repository status",
19
19
+
Long: `Show repository status and statistics
20
20
+
21
21
+
Displays overview of the bundle repository including bundles,
22
22
+
storage, timeline, mempool, and DID index status.`,
23
23
+
24
24
+
Example: ` # Show status
25
25
+
plcbundle status
26
26
+
27
27
+
# Using alias
28
28
+
plcbundle info`,
29
29
+
30
30
+
RunE: func(cmd *cobra.Command, args []string) error {
31
31
+
mgr, dir, err := getManagerFromCommand(cmd, "")
32
32
+
if err != nil {
33
33
+
return err
34
34
+
}
35
35
+
defer mgr.Close()
36
36
+
37
37
+
return showStatus(mgr, dir)
38
38
+
},
39
39
+
}
40
40
+
41
41
+
return cmd
42
42
+
}
43
43
+
44
44
+
func showStatus(mgr *bundle.Manager, dir string) error {
45
45
+
index := mgr.GetIndex()
46
46
+
stats := index.GetStats()
47
47
+
bundleCount := stats["bundle_count"].(int)
48
48
+
49
49
+
// Header
50
50
+
fmt.Printf("═══════════════════════════════════════════════════════════════\n\n")
51
51
+
fmt.Printf("\n PLC Bundle Repository Status\n")
52
52
+
fmt.Printf("═══════════════════════════════════════════════════════════════\n\n")
53
53
+
54
54
+
// Location & Origin
55
55
+
fmt.Printf("📁 %s\n", dir)
56
56
+
if origin := index.Origin; origin != "" {
57
57
+
fmt.Printf("🌐 %s\n", origin)
58
58
+
}
59
59
+
fmt.Printf("\n")
60
60
+
61
61
+
// Empty repository
62
62
+
if bundleCount == 0 {
63
63
+
fmt.Printf("⚠️ Empty repository (no bundles)\n\n")
64
64
+
fmt.Printf("Get started:\n")
65
65
+
fmt.Printf(" plcbundle clone <url> Clone from remote\n")
66
66
+
fmt.Printf(" plcbundle sync Fetch from PLC directory\n\n")
67
67
+
return nil
68
68
+
}
69
69
+
70
70
+
// Extract stats
71
71
+
firstBundle := stats["first_bundle"].(int)
72
72
+
lastBundle := stats["last_bundle"].(int)
73
73
+
totalCompressedSize := stats["total_size"].(int64)
74
74
+
totalUncompressedSize := stats["total_uncompressed_size"].(int64)
75
75
+
startTime := stats["start_time"].(time.Time)
76
76
+
endTime := stats["end_time"].(time.Time)
77
77
+
updatedAt := stats["updated_at"].(time.Time)
78
78
+
79
79
+
// Bundles summary
80
80
+
ratio := float64(totalUncompressedSize) / float64(totalCompressedSize)
81
81
+
avgSize := totalCompressedSize / int64(bundleCount)
82
82
+
fmt.Printf("📦 Bundles\n")
83
83
+
fmt.Printf(" Count: %s (%06d → %06d)\n",
84
84
+
formatNumber(bundleCount), firstBundle, lastBundle)
85
85
+
fmt.Printf(" Compressed: %s (avg %s/bundle)\n",
86
86
+
formatBytes(totalCompressedSize), formatBytes(avgSize))
87
87
+
fmt.Printf(" Uncompressed: %s (%.2fx compression)\n\n",
88
88
+
formatBytes(totalUncompressedSize), ratio)
89
89
+
90
90
+
// Timeline
91
91
+
duration := endTime.Sub(startTime)
92
92
+
age := time.Since(updatedAt)
93
93
+
fmt.Printf("📅 Timeline\n")
94
94
+
fmt.Printf(" Coverage: %s → %s\n",
95
95
+
startTime.Format("2006-01-02 15:04"), endTime.Format("2006-01-02 15:04"))
96
96
+
fmt.Printf(" Timespan: %s\n", formatDuration(duration))
97
97
+
fmt.Printf(" Last Updated: %s ago\n\n", formatDuration(age))
98
98
+
99
99
+
// Operations
100
100
+
mempoolStats := mgr.GetMempoolStats()
101
101
+
mempoolCount := mempoolStats["count"].(int)
102
102
+
bundleOpsCount := bundleCount * types.BUNDLE_SIZE
103
103
+
totalOps := bundleOpsCount + mempoolCount
104
104
+
105
105
+
fmt.Printf("🔢 Operations\n")
106
106
+
if mempoolCount > 0 {
107
107
+
fmt.Printf(" Total: %s records (%s bundled + %s mempool)\n",
108
108
+
formatNumber(totalOps), formatNumber(bundleOpsCount), formatNumber(mempoolCount))
109
109
+
} else {
110
110
+
fmt.Printf(" Total: %s records\n", formatNumber(bundleOpsCount))
111
111
+
}
112
112
+
113
113
+
if duration.Hours() > 0 {
114
114
+
opsPerHour := float64(bundleOpsCount) / duration.Hours()
115
115
+
fmt.Printf(" Rate: %.0f ops/hour\n", opsPerHour)
116
116
+
}
117
117
+
fmt.Printf("\n")
118
118
+
119
119
+
// Mempool
120
120
+
if mempoolCount > 0 {
121
121
+
targetBundle := mempoolStats["target_bundle"].(int)
122
122
+
canCreate := mempoolStats["can_create_bundle"].(bool)
123
123
+
progress := float64(mempoolCount) / float64(types.BUNDLE_SIZE) * 100
124
124
+
125
125
+
fmt.Printf("🔄 Mempool (next bundle: %06d)\n", targetBundle)
126
126
+
fmt.Printf(" Operations: %s / %s (%.1f%%)\n",
127
127
+
formatNumber(mempoolCount), formatNumber(types.BUNDLE_SIZE), progress)
128
128
+
129
129
+
// Progress bar
130
130
+
barWidth := 40
131
131
+
filled := int(float64(barWidth) * progress / 100)
132
132
+
if filled > barWidth {
133
133
+
filled = barWidth
134
134
+
}
135
135
+
bar := strings.Repeat("█", filled) + strings.Repeat("░", barWidth-filled)
136
136
+
fmt.Printf(" [%s]\n", bar)
137
137
+
138
138
+
if canCreate {
139
139
+
fmt.Printf(" ✓ Ready to create bundle\n")
140
140
+
} else {
141
141
+
remaining := types.BUNDLE_SIZE - mempoolCount
142
142
+
fmt.Printf(" Need %s more operations\n", formatNumber(remaining))
143
143
+
}
144
144
+
fmt.Printf("\n")
145
145
+
}
146
146
+
147
147
+
// DID Index
148
148
+
didStats := mgr.GetDIDIndexStats()
149
149
+
if didStats["exists"].(bool) {
150
150
+
indexedDIDs := didStats["indexed_dids"].(int64)
151
151
+
mempoolDIDs := didStats["mempool_dids"].(int64)
152
152
+
totalDIDs := didStats["total_dids"].(int64)
153
153
+
lastIndexedBundle := didStats["last_bundle"].(int)
154
154
+
shardCount := didStats["shard_count"].(int)
155
155
+
cachedShards := didStats["cached_shards"].(int)
156
156
+
157
157
+
fmt.Printf("🔍 DID Index\n")
158
158
+
if mempoolDIDs > 0 {
159
159
+
fmt.Printf(" DIDs: %s (%s indexed + %s mempool)\n",
160
160
+
formatNumber(int(totalDIDs)),
161
161
+
formatNumber(int(indexedDIDs)),
162
162
+
formatNumber(int(mempoolDIDs)))
163
163
+
} else {
164
164
+
fmt.Printf(" DIDs: %s\n", formatNumber(int(totalDIDs)))
165
165
+
}
166
166
+
167
167
+
fmt.Printf(" Shards: %d (%d cached)\n", shardCount, cachedShards)
168
168
+
169
169
+
// Index health
170
170
+
if lastIndexedBundle < lastBundle {
171
171
+
behind := lastBundle - lastIndexedBundle
172
172
+
fmt.Printf(" Status: ⚠️ Behind by %d bundle", behind)
173
173
+
if behind > 1 {
174
174
+
fmt.Printf("s")
175
175
+
}
176
176
+
fmt.Printf(" (at %06d, need %06d)\n", lastIndexedBundle, lastBundle)
177
177
+
} else {
178
178
+
fmt.Printf(" Status: ✓ Up to date (bundle %06d)\n", lastIndexedBundle)
179
179
+
}
180
180
+
fmt.Printf("\n")
181
181
+
}
182
182
+
183
183
+
// Gaps
184
184
+
gaps := index.FindGaps()
185
185
+
if len(gaps) > 0 {
186
186
+
fmt.Printf("⚠️ Missing Bundles: %d gap", len(gaps))
187
187
+
if len(gaps) > 1 {
188
188
+
fmt.Printf("s")
189
189
+
}
190
190
+
fmt.Printf("\n")
191
191
+
192
192
+
// Show gaps compactly
193
193
+
displayCount := len(gaps)
194
194
+
if displayCount > 15 {
195
195
+
displayCount = 15
196
196
+
}
197
197
+
198
198
+
fmt.Printf(" ")
199
199
+
for i := 0; i < displayCount; i++ {
200
200
+
if i > 0 {
201
201
+
fmt.Printf(", ")
202
202
+
}
203
203
+
if i > 0 && i%8 == 0 {
204
204
+
fmt.Printf("\n ")
205
205
+
}
206
206
+
fmt.Printf("%06d", gaps[i])
207
207
+
}
208
208
+
if len(gaps) > displayCount {
209
209
+
fmt.Printf(", ... +%d more", len(gaps)-displayCount)
210
210
+
}
211
211
+
fmt.Printf("\n\n")
212
212
+
}
213
213
+
214
214
+
// Chain Hashes
215
215
+
firstMeta, err := index.GetBundle(firstBundle)
216
216
+
if err == nil {
217
217
+
fmt.Printf("🔐 Chain Hashes\n")
218
218
+
fmt.Printf(" Root (bundle %06d):\n", firstBundle)
219
219
+
fmt.Printf(" %s\n", firstMeta.Hash)
220
220
+
221
221
+
lastMeta, err := index.GetBundle(lastBundle)
222
222
+
if err == nil {
223
223
+
fmt.Printf(" Head (bundle %06d):\n", lastBundle)
224
224
+
fmt.Printf(" %s\n", lastMeta.Hash)
225
225
+
}
226
226
+
fmt.Printf("\n")
227
227
+
}
228
228
+
229
229
+
return nil
230
230
+
}
+2
-2
cmd/plcbundle/main.go
···
50
50
cmd.AddCommand(commands.NewExportCommand())
51
51
cmd.AddCommand(commands.NewStreamCommand())
52
52
cmd.AddCommand(commands.NewGetCommand())
53
53
-
cmd.AddCommand(commands.NewRollbackCommand())
53
53
+
cmd.AddCommand(commands.NewRollbackCommand())*/
54
54
55
55
// Status & info (root level)
56
56
cmd.AddCommand(commands.NewStatusCommand())
57
57
-
cmd.AddCommand(commands.NewLogCommand())
57
57
+
/*cmd.AddCommand(commands.NewLogCommand())
58
58
cmd.AddCommand(commands.NewGapsCommand())
59
59
cmd.AddCommand(commands.NewVerifyCommand())
60
60
cmd.AddCommand(commands.NewDiffCommand())