EonaCat.MemoryGuard 1.1.1

Prefix Reserved
dotnet add package EonaCat.MemoryGuard --version 1.1.1
                    
NuGet\Install-Package EonaCat.MemoryGuard -Version 1.1.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="EonaCat.MemoryGuard" Version="1.1.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="EonaCat.MemoryGuard" Version="1.1.1" />
                    
Directory.Packages.props
<PackageReference Include="EonaCat.MemoryGuard" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add EonaCat.MemoryGuard --version 1.1.1
                    
#r "nuget: EonaCat.MemoryGuard, 1.1.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package EonaCat.MemoryGuard@1.1.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=EonaCat.MemoryGuard&version=1.1.1
                    
Install as a Cake Addin
#tool nuget:?package=EonaCat.MemoryGuard&version=1.1.1
                    
Install as a Cake Tool

EonaCat.MemoryGuard

** Automatic memory leak detector for .NET **

EonaCat MemoryGuard is a fully automatic memory leak detection and remediation system with ML-based predictions, pattern detection, and real-time monitoring. Just start once and let it handle everything.

🚀 Features

Fully Automatic Operation

  • Zero manual tracking - Automatically profiles ALL allocations
  • Self-optimizing with machine learning predictions
  • Auto-remediation when memory health deteriorates
  • Background analysis with zero blocking

Advanced Detection

  • 🔍 Pattern Detection: Cyclic allocation, spiral leaks, event handler leaks, collection growth, cache bloat
  • 🎯 ML-Based Predictions: Predicts future leaks before they happen
  • 📊 Health Scoring: Real-time health score (0-100) with actionable recommendations
  • 🧠 Retention Path Discovery: Finds what's holding objects in memory

Monitoring

  • 📈 Real-time allocation tracking with weak references
  • 💾 Generation-based analysis (Gen0, Gen1, Gen2)
  • 🔥 Large Object Heap (LOH) monitoring
  • 📉 Heap fragmentation analysis
  • ⚡ Performance metrics and allocation rates

Auto-Remediation

  • 🔧 Automatic leak fixing attempts
  • 🚨 Emergency optimization on OutOfMemoryException
  • 📋 Complete remediation history
  • ⚙️ Configurable remediation strategies

Reporting

  • 📄 JSON reports auto-saved to disk
  • 🎨 Pretty console output with emojis
  • 📑 Programmatic report generation

📦 Installation

Via NuGet

dotnet add package EonaCat.MemoryGuard

🎯 Quick Start

One-Line Initialization (Development)

using EonaCat.MemoryGuard;

// That's it! Everything is now monitored automatically!
MemoryGuard.StartDevelopment();

One-Line Initialization (Production)

// Optimized for production with auto-reports and auto-remediation
MemoryGuard.StartProduction();

Custom Configuration

using EonaCat.MemoryGuard;

// Fluent API for custom configuration
new MemoryGuardBuilder()
    .WithMonitoringInterval(TimeSpan.FromSeconds(15))
    .WithMemoryThreshold(4L * 1024 * 1024 * 1024) // 4GB
    .WithAutoRemediation(true)
    .WithAutoReports(true)
    .WithStackTraces(true)
    .Start();

Manual Configuration

var config = new MemoryGuardConfiguration
{
    MonitoringInterval = TimeSpan.FromSeconds(30),
    AnalysisInterval = TimeSpan.FromMinutes(2),
    PredictionInterval = TimeSpan.FromMinutes(5),
    MemoryPressureThreshold = 2L * 1024 * 1024 * 1024, // 2GB
    CaptureStackTraces = true,
    EnableAutoRemediation = true,
    AutoSaveReports = true
};

MemoryGuard.Start(_config);

💡 Usage Examples

Subscribe to Events

var detector = MemoryGuard.Instance;

// Leak detection
detector.LeakDetected += (s, e) =>
{
    Console.WriteLine($"🔴 LEAK DETECTED");
    Console.WriteLine($"   Type: {e.LeakInfo.TypeName}");
    Console.WriteLine($"   Age: {e.LeakInfo.Age}");
    Console.WriteLine($"   Size: {e.LeakInfo.EstimatedSize.ToHumanReadable()}");
    Console.WriteLine($"   Retention Path: {e.LeakInfo.RetentionPath}");
};

// Memory pressure
detector.MemoryPressureDetected += (s, e) =>
{
    Console.WriteLine($"⚠️ MEMORY PRESSURE");
    Console.WriteLine($"   Current: {e.CurrentMemory.ToHumanReadable()}");
    Console.WriteLine($"   Threshold: {e.Threshold.ToHumanReadable()}");
    Console.WriteLine($"   Health: {e.HealthScore.Grade}");
    Console.WriteLine($"   Recommendation: {e.Recommendation}");
    Console.WriteLine($"   Time to OOM: {e.PredictedTimeToOOM}");
};

// Pattern detection
detector.PatternDetected += (s, e) =>
{
    Console.WriteLine($"{e.Pattern.Severity.GetSeverityEmoji()} PATTERN DETECTED");
    Console.WriteLine($"   Type: {e.Pattern.Type}");
    Console.WriteLine($"   Description: {e.Pattern.Description}");
    Console.WriteLine($"   Confidence: {e.Pattern.Confidence:P0}");
    Console.WriteLine($"   Action: {e.Pattern.RecommendedAction}");
};

// Large allocations
detector.LargeAllocationDetected += (s, e) =>
{
    Console.WriteLine($"📦 LARGE ALLOCATION");
    Console.WriteLine($"   Type: {e.Type.Name}");
    Console.WriteLine($"   Size: {e.Size.ToHumanReadable()}");
    Console.WriteLine($"   Is LOH: {e.IsLOH}");
};

// Leak predictions
detector.LeakPredicted += (s, e) =>
{
    Console.WriteLine($"🔮 LEAK PREDICTION");
    Console.WriteLine($"   Type: {e.PredictedType.Name}");
    Console.WriteLine($"   Confidence: {e.Confidence:P0}");
    Console.WriteLine($"   ETA: {e.EstimatedTimeToLeak}");
    Console.WriteLine($"   Action: {e.RecommendedAction}");
};

// Auto-remediation
detector.AutoRemediationPerformed += (s, e) =>
{
    Console.WriteLine($"🔧 AUTO-REMEDIATION");
    Console.WriteLine($"   Action: {e.Action.ActionTaken}");
    Console.WriteLine($"   Success: {e.Action.Success}");
    Console.WriteLine($"   Time: {e.Action.Timestamp}");
};

Get Instant Health Check

var health = MemoryGuard.GetHealthCheck();
Console.WriteLine(health);

// Output:
// ╔══════════════════════════════════════════╗
// ║      EONACAT MEMORY GUARD                ║
// ║         HEALTH CHECK                     ║
// ╠══════════════════════════════════════════╣
// ║  🟢 GOOD - 75/100                        ║
// ║  Memory: 512.5 MB                        ║
// ║  Objects: 45,231                         ║
// ║  Rate: 2.3 MB/sec                        ║
// ║  GC Pressure: 0.15/min                   ║
// ╚══════════════════════════════════════════╝

Generate Reports

// Beautiful console report
await MemoryGuard.PrintReportAsync();

// Programmatic access
var report = await MemoryGuard.Instance.GenerateAdvancedReportAsync();
Console.WriteLine($"Health: {report.HealthScore.Grade}");
Console.WriteLine($"Leaks: {report.DetectedPatterns.Count}");
Console.WriteLine($"Top Allocator: {report.TopAllocators.First().Type.Name}");

// Simple report
var simpleReport = MemoryGuard.Instance.GenerateReport();

Manual Analysis

// Trigger analysis
var analysis = await MemoryGuard.Instance.AnalyzeAsync();

Console.WriteLine($"Memory: {analysis.TotalMemoryBytes.ToHumanReadable()}");
Console.WriteLine($"Leaked Objects: {analysis.LeakedObjects.Count}");
Console.WriteLine($"Suspicious Objects: {analysis.SuspiciousObjects.Count}");
Console.WriteLine($"Patterns: {analysis.DetectedPatterns.Count}");
Console.WriteLine($"Health Score: {analysis.HealthScore.Score:F0}/100");
Console.WriteLine($"Fragmentation: {analysis.HeapFragmentation.FragmentationPercent:F2}%");

// View leaked objects
foreach (var leak in analysis.LeakedObjects)
{
    Console.WriteLine($"  - {leak.TypeName}");
    Console.WriteLine($"    Age: {leak.Age}");
    Console.WriteLine($"    Size: {leak.EstimatedSize.ToHumanReadable()}");
    Console.WriteLine($"    Generation: {leak.Generation}");
    Console.WriteLine($"    Thread: {leak.ThreadId}");
}

Get Statistics

var stats = MemoryGuard.Instance.GetStatistics();

Console.WriteLine($"Total Allocations: {stats.TotalAllocations:N0}");
Console.WriteLine($"Alive Objects: {stats.AliveObjects:N0}");
Console.WriteLine($"Collected Objects: {stats.CollectedObjects:N0}");
Console.WriteLine($"Total Memory: {stats.TotalGCMemory.ToHumanReadable()}");
Console.WriteLine($"Allocation Rate: {stats.AllocationRate.ToHumanReadable()}/sec");
Console.WriteLine($"GC Pressure: {stats.GCPressure:F2} Gen2/min");
Console.WriteLine($"Gen0: {stats.Gen0Objects:N0} objects ({stats.Gen0Collections:N0} collections)");
Console.WriteLine($"Gen1: {stats.Gen1Objects:N0} objects ({stats.Gen1Collections:N0} collections)");
Console.WriteLine($"Gen2: {stats.Gen2Objects:N0} objects ({stats.Gen2Collections:N0} collections)");

View Top Allocators

var topTypes = MemoryGuard.Instance.GetTopAllocators(10);

foreach (var type in topTypes)
{
    Console.WriteLine($"{type.Type.Name}:");
    Console.WriteLine($"  Total Size: {type.TotalSize.ToHumanReadable()}");
    Console.WriteLine($"  Count: {type.AllocCount:N0}");
    Console.WriteLine($"  Average: {type.AverageSize.ToHumanReadable()}");
    Console.WriteLine($"  Peak: {type.PeakSize.ToHumanReadable()}");
    Console.WriteLine($"  Rate: {type.AllocationRate:F2} allocs/sec");
}

Manual Optimization

// Force garbage collection and cleanup
var result = MemoryGuard.Instance.ForceOptimize();

Console.WriteLine($"Before: {result.BeforeMemory.ToHumanReadable()}");
Console.WriteLine($"After: {result.AfterMemory.ToHumanReadable()}");
Console.WriteLine($"Freed: {result.FreedBytes.ToHumanReadable()}");
Console.WriteLine($"Cleared Objects: {result.ClearedObjects:N0}");
Console.WriteLine($"Removed Allocations: {result.RemovedTrackedAllocations:N0}");

Shutdown

// Stop monitoring and generate final report
MemoryGuard.Shutdown();

📊 What Happens Automatically

Once started, MemoryGuard automatically:

  1. Tracks all allocations with weak references (no memory overhead)
  2. Monitors memory every 30 seconds (configurable)
  3. Analyzes for leaks every 2 minutes (configurable)
  4. Detects patterns every 3 minutes (configurable)
  5. Predicts future leaks every 5 minutes (configurable)
  6. Attempts remediation when health drops below 40
  7. Emergency optimization on OutOfMemoryException
  8. Saves reports to disk automatically
  9. Cleans up dead references in background
  10. Generates final report on application exit

🎯 Advanced Features

Pattern Detection

MemoryGuard automatically detects these memory patterns:

  • Cyclic Allocation: Repeating memory allocation patterns
  • Spiral Leak: Continuous growth with increasing volatility
  • Event Handler Leak: Unsubscribed event handlers retaining objects
  • Collection Growth: Unbounded collection growth
  • Cache Bloat: Oversized caches without eviction policies

Health Scoring System

Real-time health score from 0-100 with grades:

  • 90-100: ✅ Excellent - Everything optimal
  • 70-89: 🟢 Good - Normal operation
  • 50-69: 🟡 Fair - Some concerns
  • 30-49: 🟠 Poor - Action needed
  • 0-29: 🔴 Critical - Immediate attention required

ML-Based Predictions

  • Predicts which types will leak based on historical patterns
  • Estimates time to OutOfMemoryException
  • Confidence scoring for each prediction
  • Recommends preventive actions

Auto-Remediation

When enabled, MemoryGuard automatically:

  • Logs detected leaks for investigation
  • Performs optimized garbage collection
  • Emergency memory cleanup when critical
  • Tracks all remediation attempts

⚙️ Configuration Options

public class MemoryGuardConfiguration
{
    // Monitoring intervals
    TimeSpan MonitoringInterval { get; set; }          // Default: 30s
    TimeSpan AnalysisInterval { get; set; }            // Default: 2m
    TimeSpan PredictionInterval { get; set; }          // Default: 5m
    TimeSpan BackgroundAnalysisInterval { get; set; }  // Default: 1m
    TimeSpan OptimizationInterval { get; set; }        // Default: 10m
    TimeSpan PatternDetectionInterval { get; set; }    // Default: 3m
    
    // Thresholds
    TimeSpan LeakDetectionThreshold { get; set; }      // Default: 5m
    TimeSpan SuspiciousObjectThreshold { get; set; }   // Default: 2m
    long MemoryPressureThreshold { get; set; }         // Default: 2GB
    long LargeAllocationThreshold { get; set; }        // Default: 85KB (LOH)
    
    // Features
    bool CaptureStackTraces { get; set; }              // Default: true
    bool EnableLargeObjectHeapCompaction { get; set; } // Default: true
    bool GenerateReportOnExit { get; set; }            // Default: true
    bool AutoSaveReports { get; set; }                 // Default: true
    bool EnableAutoRemediation { get; set; }           // Default: true
    
    // Limits
    int MaxSnapshotsRetained { get; set; }             // Default: 200
}

Preset Configurations

// Default - Balanced for most scenarios
MemoryGuardConfiguration.Default

// Production - Optimized for production environments
MemoryGuardConfiguration.Production

// Development - Verbose monitoring for debugging
MemoryGuardConfiguration.Development

🌐 Platform Support

  • .NET Framework 4.6.1+
  • .NET Core 2.0+
  • .NET 5/6/7/8
  • Windows
  • Linux
  • macOS

No external dependencies required!

📁 Report Output

Reports are automatically saved to:

{ApplicationDirectory}/EonaCatMemoryGuard/Reports/MemoryReport_YYYYMMDD_HHMMSS.json

Example report structure:

{
  "GeneratedAt": "2025-01-07T15:30:00.000Z",
  "Uptime": "02:15:30",
  "TotalTrackedAllocations": 125847,
  "HealthScore": 75.5,
  "HealthGrade": "Good",
  "TotalMemory": 536870912,
  "AliveObjects": 45231,
  "LeakCount": 3,
  "PatternCount": 2
}

🎯 Automatic Tracking Options

Track objects as you create them:

// Start MemoryGuard
MemoryGuard.StartDevelopment();

// Track individual objects
var myObject = MemoryGuard.Track(new MyClass());
var list = MemoryGuard.Track(new List());

// Track with identifier
var cache = MemoryGuard.Track(new Dictionary(), "MainCache");

// Track collections
var items = Enumerable.Range(0, 100).Select(i => new Item(i)).ToList();
MemoryGuard.TrackRange(items);

Option 2: Scoped Tracking

Track all objects within a scope:

using (var scope = MemoryGuard.CreateScope("ProcessData"))
{
    var data = scope.Track(new DataProcessor());
    var buffer = scope.Track(new byte[1024 * 1024]);
    
    data.Process(buffer);
    
    // At disposal, warns if too many objects still alive
}

Option 3: Object Tracker

Create a tracker for a category of objects:

var tracker = MemoryGuard.CreateTracker("UserSessions");

// Track user session objects
foreach (var user in users)
{
    var session = tracker.Track(new UserSession(user));
}

// Check how many are still alive
Console.WriteLine($"Active sessions: {tracker.GetAliveCount()}");

Option 4: Extension Method Style

// Usage
var item = new MyClass().Tracked();
var list = new List().Tracked();

Example 1: Event Handler Leak Detection

MemoryGuard.StartDevelopment();

var publisher = MemoryGuard.Track(new EventPublisher());

// Track subscribers
for (int i = 0; i < 100; i++)
{
    var subscriber = MemoryGuard.Track(new EventSubscriber(i));
    publisher.SomeEvent += subscriber.HandleEvent;
    // FORGOT TO UNSUBSCRIBE - MemoryGuard will detect!
}

// Wait for detection
await Task.Delay(10000);

// MemoryGuard will report:
// 🔴 LEAK DETECTED!
//    Type: EventSubscriber
//    Root Cause: EventHandlerNotUnsubscribed
//    Fix: Unsubscribe from events: publisher.SomeEvent -= subscriber.HandleEvent;

Example 2: Cache Without Eviction

MemoryGuard.StartDevelopment();

var cache = MemoryGuard.Track(new Dictionary(), "DataCache");

// Fill cache without limits
for (int i = 0; i < 10000; i++)
{
    cache[$"key_{i}"] = new byte[1024];
}

// MemoryGuard will detect:
// 🔴 PATTERN DETECTED!
//    Type: CacheBloat
//    Recommendation: Implement cache eviction policy

Example 3: Static Reference Leak

public static class GlobalCache
{
    private static List _items = new List();
    
    public static void Add(Data item)
    {
        _items.Add(MemoryGuard.Track(item));
    }
}

// MemoryGuard will detect:
// 🔴 LEAK DETECTED!
//    Root Cause: StaticReference  
//    Fix: Remove from static collection when done

Example 4: Scoped Tracking for Request Processing

public async Task ProcessRequest(HttpRequest request)
{
    using (var scope = MemoryGuard.CreateScope($"Request-{request.Id}"))
    {
        var processor = scope.Track(new RequestProcessor());
        var context = scope.Track(new ProcessingContext());
        
        await processor.ProcessAsync(context);
        
        // If objects leak, you'll get a warning with the request ID
    }
}

Example 5: Testing for Leaks

[Test]
public async Task TestNoMemoryLeaks()
{
    MemoryGuard.StartDevelopment();
    var tracker = MemoryGuard.CreateTracker("TestObjects");
    
    // Run your code
    for (int i = 0; i < 1000; i++)
    {
        var obj = tracker.Track(new TestObject());
        obj.Process();
    }
    
    // Force GC
    GC.Collect();
    GC.WaitForPendingFinalizers();
    GC.Collect();
    
    // Check for leaks
    await Task.Delay(5000);
    var analysis = await MemoryGuard.Instance.AnalyzeAsync();
    
    Assert.AreEqual(0, analysis.LeakedObjects.Count, 
        "Memory leaks detected!");
}

💡 When to Track What

Object Type When to Track Why
Event Subscribers Always High leak risk
Caches/Dictionaries Always Can grow unbounded
Timers Always Must be disposed
Threads Always Can prevent shutdown
Large objects (>85KB) Always LOH issues
Collections If long-lived Growth patterns
Request/Context objects In scope Lifecycle tracking
Test objects In tests Leak verification

🐛 Troubleshooting

High Memory Usage from MemoryGuard Itself

Reduce tracking overhead:

var config = MemoryGuardConfiguration.Production;
config.CaptureStackTraces = false;  // Saves significant memory
config.MaxSnapshotsRetained = 50;   // Reduce snapshot history
MemoryGuard.Start(config);

Missing Reports

Check write permissions:

var reportPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "EonaCatMemoryGuard", "Reports");
Console.WriteLine($"Reports saved to: {reportPath}");

Events Not Firing

Ensure you've started monitoring:

MemoryGuard.Start();
// Events are only fired after Start() is called internally

False Leak Detections

Adjust thresholds:

var config = new MemoryGuardConfiguration();
config.LeakDetectionThreshold = TimeSpan.FromMinutes(10); // Longer threshold
config.SuspiciousObjectThreshold = TimeSpan.FromMinutes(5);
MemoryGuard.Start(config);

📝 Best Practices

Development

// Verbose monitoring with stack traces
MemoryGuard.StartDevelopment();

// Subscribe to all events for visibility
MemoryGuard.Instance.LeakDetected += (s, e) => Debug.WriteLine($"Leak: {e.LeakInfo.TypeName}");
MemoryGuard.Instance.PatternDetected += (s, e) => Debug.WriteLine($"Pattern: {e.Pattern.Type}");

Production

// Optimized settings with auto-remediation
MemoryGuard.StartProduction();

// Only log critical events
MemoryGuard.Instance.MemoryPressureDetected += (s, e) => 
{
    if (e.HealthScore.Score < 30)
        Logger.Critical($"Memory critical: {e.Recommendation}");
};

Testing

// Monitor a specific test
MemoryGuard.StartDevelopment();

// Run your test
RunMemoryIntensiveTest();

// Get results
var analysis = await MemoryGuard.Instance.AnalyzeAsync();
Assert.True(analysis.LeakedObjects.Count == 0, "Memory leaks detected!");

// Print report
await MemoryGuard.PrintReportAsync();

Continuous Integration

// Add to your CI pipeline
[TestMethod]
public async Task TestForMemoryLeaks()
{
    MemoryGuard.StartDevelopment();
    
    // Run your application logic
    await RunApplicationWorkload();
    
    // Analyze for leaks
    var analysis = await MemoryGuard.Instance.AnalyzeAsync();
    
    // Fail build if leaks detected
    Assert.AreEqual(0, analysis.LeakedObjects.Count, 
        $"Detected {analysis.LeakedObjects.Count} memory leaks!");
    
    // Save report as artifact
    var report = await MemoryGuard.Instance.GenerateAdvancedReportAsync();
    // Upload report to CI artifacts
}

Using the Generator

Install-Package EonaCat.MemoryGuard.Generator

1. Make your class a partial class

public partial class MyClass
{
    
}

To Create the class with auto tracking:

var myPartialClassObject = MyClass.Create();

2. Make your class normally

public class MyClass
{
    
}

Use the Factory for normal classes:

var myClassObject = MemoryGuardFactory_MyClass.Create();

Stop hunting memory leaks manually - let EonaCat MemoryGuard do it automatically!

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.1 is compatible. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on EonaCat.MemoryGuard:

Package Downloads
EonaCat.MemoryGuard.Generator

MemoryGuard Auto-Track Source Generator

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.1.1 141 1/19/2026
1.1.0 90 1/19/2026
1.0.9 97 1/12/2026
1.0.8 97 1/12/2026
1.0.7 93 1/12/2026
1.0.6 92 1/12/2026
1.0.5 95 1/12/2026
1.0.4 175 1/11/2026
1.0.3 99 1/11/2026
1.0.2 104 1/9/2026
1.0.1 209 1/9/2026
1.0.0 100 1/8/2026