[DEPRECATED] Go implementation of plcbundle

status cmd

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