This commit is contained in:
StellaOps Bot
2025-12-14 23:20:14 +02:00
parent 3411e825cd
commit b058dbe031
356 changed files with 68310 additions and 1108 deletions

103
templates/README.md Normal file
View File

@@ -0,0 +1,103 @@
# StellaOps Plugin Templates
This directory contains `dotnet new` templates for creating StellaOps plugins.
## Available Templates
| Template | Short Name | Description |
|----------|------------|-------------|
| StellaOps Connector Plugin | `stellaops-plugin-connector` | Create a data connector plugin (e.g., VEX, advisory feeds) |
| StellaOps Scheduled Job Plugin | `stellaops-plugin-scheduler` | Create a scheduled job plugin |
## Installation
### From Local Directory
```bash
dotnet new install ./templates
```
### From NuGet Package
```bash
dotnet new install StellaOps.Templates
```
## Usage
### Create a Connector Plugin
```bash
# Create with defaults
dotnet new stellaops-plugin-connector -n MyCompany.AcmeConnector
# Create with custom parameters
dotnet new stellaops-plugin-connector \
-n MyCompany.AcmeConnector \
--connectorName Acme \
--connectorId acme-vex \
--namespace MyCompany.Plugins.Acme
```
### Create a Scheduled Job Plugin
```bash
# Create with defaults
dotnet new stellaops-plugin-scheduler -n MyCompany.CleanupJob
# Create with custom parameters
dotnet new stellaops-plugin-scheduler \
-n MyCompany.CleanupJob \
--jobName Cleanup \
--cronSchedule "0 */6 * * *" \
--namespace MyCompany.Plugins.Cleanup
```
## Template Parameters
### Connector Plugin Parameters
| Parameter | Description | Default |
|-----------|-------------|---------|
| `--connectorName` | Name of the connector | MyConnector |
| `--connectorId` | Unique connector identifier | my-connector |
| `--namespace` | Root namespace | StellaOps.Plugin.MyConnector |
| `--pluginVersion` | Initial version | 1.0.0 |
### Scheduler Plugin Parameters
| Parameter | Description | Default |
|-----------|-------------|---------|
| `--jobName` | Name of the scheduled job | MyJob |
| `--cronSchedule` | Default cron schedule | 0 0 * * * |
| `--namespace` | Root namespace | StellaOps.Plugin.MyJob |
| `--pluginVersion` | Initial version | 1.0.0 |
## Building the Template Pack
```bash
cd templates
dotnet pack -c Release
```
This creates `StellaOps.Templates.<version>.nupkg` that can be published to NuGet.
## Uninstalling Templates
```bash
dotnet new uninstall StellaOps.Templates
```
## Plugin Development Guide
After creating a plugin from a template:
1. **Update options**: Modify the `*Options.cs` file with your configuration
2. **Implement logic**: Add your business logic to the main class
3. **Add validation**: Update the validator for your specific requirements
4. **Add tests**: Create a test project for your plugin
5. **Build**: `dotnet build -c Release`
6. **Sign**: `cosign sign --key $COSIGN_KEY <assembly.dll>`
7. **Deploy**: Copy to the plugin binaries directory
For more details, see `docs/10_PLUGIN_SDK_GUIDE.md`.

View File

@@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<PackageType>Template</PackageType>
<PackageVersion>1.0.0</PackageVersion>
<PackageId>StellaOps.Templates</PackageId>
<Title>StellaOps Plugin Templates</Title>
<Authors>StellaOps</Authors>
<Description>Templates for creating StellaOps plugins including connectors and scheduled jobs.</Description>
<PackageTags>dotnet-new;templates;stellaops;plugin</PackageTags>
<PackageLicenseExpression>AGPL-3.0-or-later</PackageLicenseExpression>
<PackageProjectUrl>https://stellaops.io</PackageProjectUrl>
<RepositoryUrl>https://git.stella-ops.org/stella-ops.org/git.stella-ops.org</RepositoryUrl>
<TargetFramework>net10.0</TargetFramework>
<IncludeContentInPack>true</IncludeContentInPack>
<IncludeBuildOutput>false</IncludeBuildOutput>
<ContentTargetFolders>content</ContentTargetFolders>
<NoWarn>$(NoWarn);NU5128</NoWarn>
<NoDefaultExcludes>true</NoDefaultExcludes>
</PropertyGroup>
<ItemGroup>
<Content Include="stellaops-plugin-connector\**\*" Exclude="stellaops-plugin-connector\**\bin\**;stellaops-plugin-connector\**\obj\**" />
<Content Include="stellaops-plugin-scheduler\**\*" Exclude="stellaops-plugin-scheduler\**\bin\**;stellaops-plugin-scheduler\**\obj\**" />
<Compile Remove="**\*" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,54 @@
{
"$schema": "http://json.schemastore.org/template",
"author": "StellaOps",
"classifications": ["StellaOps", "Plugin", "Connector"],
"identity": "StellaOps.Plugin.Connector",
"name": "StellaOps Connector Plugin",
"shortName": "stellaops-plugin-connector",
"description": "A template for creating StellaOps connector plugins (e.g., VEX, advisory, or feed connectors)",
"tags": {
"language": "C#",
"type": "project"
},
"sourceName": "MyConnector",
"preferNameDirectory": true,
"symbols": {
"connectorName": {
"type": "parameter",
"datatype": "text",
"description": "The name of the connector (e.g., 'Acme' for AcmeConnector)",
"defaultValue": "MyConnector",
"replaces": "MyConnector",
"fileRename": "MyConnector"
},
"connectorId": {
"type": "parameter",
"datatype": "text",
"description": "The unique connector identifier (e.g., 'acme-vex')",
"defaultValue": "my-connector",
"replaces": "my-connector"
},
"namespace": {
"type": "parameter",
"datatype": "text",
"description": "The root namespace for the plugin",
"defaultValue": "StellaOps.Plugin.MyConnector",
"replaces": "StellaOps.Plugin.MyConnector"
},
"pluginVersion": {
"type": "parameter",
"datatype": "text",
"description": "The initial plugin version",
"defaultValue": "1.0.0",
"replaces": "1.0.0-template"
}
},
"postActions": [
{
"description": "Restore NuGet packages required by this project",
"manualInstructions": [{ "text": "Run 'dotnet restore'" }],
"actionId": "210D431B-A78B-4D2F-B762-4ED3E3EA9025",
"continueOnError": true
}
]
}

View File

@@ -0,0 +1,26 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using StellaOps.DependencyInjection;
using StellaOps.DependencyInjection.Validation;
namespace StellaOps.Plugin.MyConnector;
/// <summary>
/// Registers MyConnector services with the dependency injection container.
/// </summary>
public sealed class MyConnectorDependencyInjectionRoutine : IDependencyInjectionRoutine
{
/// <inheritdoc />
public IServiceCollection Register(IServiceCollection services, IConfiguration configuration)
{
// Register options with fail-fast validation
services.AddOptionsWithValidation<MyConnectorOptions, MyConnectorOptionsValidator>(
MyConnectorOptions.SectionName);
// Register the connector plugin
services.AddSingleton<IConnectorPlugin, MyConnectorPlugin>();
return services;
}
}

View File

@@ -0,0 +1,105 @@
using Microsoft.Extensions.Logging;
using StellaOps.Plugin;
namespace StellaOps.Plugin.MyConnector;
/// <summary>
/// Connector implementation for MyConnector.
/// </summary>
public sealed class MyConnector : IFeedConnector
{
private readonly ILogger<MyConnector> _logger;
private readonly MyConnectorOptions _options;
public MyConnector(ILogger<MyConnector> logger, MyConnectorOptions options)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_options = options ?? throw new ArgumentNullException(nameof(options));
}
/// <summary>
/// Gets the unique identifier for this connector.
/// </summary>
public string Id => "my-connector";
/// <summary>
/// Gets the display name for this connector.
/// </summary>
public string DisplayName => "My Connector";
/// <inheritdoc />
public async Task<FetchResult> FetchAsync(FetchContext context, CancellationToken cancellationToken = default)
{
_logger.LogInformation("Fetching data from {ConnectorId}...", Id);
// TODO: Implement your fetch logic here
// Example: Download data from an external source
await Task.Delay(100, cancellationToken); // Placeholder
return new FetchResult(
Success: true,
Data: Array.Empty<byte>(),
ContentType: "application/json",
ETag: null);
}
/// <inheritdoc />
public async Task<ParseResult> ParseAsync(byte[] data, CancellationToken cancellationToken = default)
{
_logger.LogInformation("Parsing data from {ConnectorId}...", Id);
// TODO: Implement your parsing logic here
await Task.Yield();
return new ParseResult(
Success: true,
Items: Array.Empty<object>(),
Errors: Array.Empty<string>());
}
/// <inheritdoc />
public async Task<MapResult> MapAsync(object item, CancellationToken cancellationToken = default)
{
_logger.LogInformation("Mapping item from {ConnectorId}...", Id);
// TODO: Implement your mapping logic here
await Task.Yield();
return new MapResult(
Success: true,
MappedItem: item,
Errors: Array.Empty<string>());
}
}
/// <summary>
/// Result of a fetch operation.
/// </summary>
public sealed record FetchResult(
bool Success,
byte[] Data,
string? ContentType,
string? ETag);
/// <summary>
/// Result of a parse operation.
/// </summary>
public sealed record ParseResult(
bool Success,
IReadOnlyList<object> Items,
IReadOnlyList<string> Errors);
/// <summary>
/// Result of a map operation.
/// </summary>
public sealed record MapResult(
bool Success,
object? MappedItem,
IReadOnlyList<string> Errors);
/// <summary>
/// Context for fetch operations.
/// </summary>
public sealed record FetchContext(
string? LastETag = null,
DateTimeOffset? LastFetchTime = null);

View File

@@ -0,0 +1,42 @@
using System.ComponentModel.DataAnnotations;
namespace StellaOps.Plugin.MyConnector;
/// <summary>
/// Configuration options for the MyConnector plugin.
/// </summary>
public sealed class MyConnectorOptions
{
/// <summary>
/// The configuration section name.
/// </summary>
public const string SectionName = "Plugins:MyConnector";
/// <summary>
/// Gets or sets the base URL for the connector's data source.
/// </summary>
[Required]
public required string BaseUrl { get; set; }
/// <summary>
/// Gets or sets the API key for authentication.
/// </summary>
public string? ApiKey { get; set; }
/// <summary>
/// Gets or sets the request timeout in seconds. Default is 30.
/// </summary>
[Range(1, 300)]
public int TimeoutSeconds { get; set; } = 30;
/// <summary>
/// Gets or sets the maximum number of retry attempts. Default is 3.
/// </summary>
[Range(0, 10)]
public int MaxRetries { get; set; } = 3;
/// <summary>
/// Gets or sets whether to enable verbose logging.
/// </summary>
public bool EnableVerboseLogging { get; set; }
}

View File

@@ -0,0 +1,27 @@
using Microsoft.Extensions.Options;
using StellaOps.DependencyInjection.Validation;
namespace StellaOps.Plugin.MyConnector;
/// <summary>
/// Validates <see cref="MyConnectorOptions"/> configuration.
/// </summary>
public sealed class MyConnectorOptionsValidator : OptionsValidatorBase<MyConnectorOptions>
{
protected override string SectionPrefix => MyConnectorOptions.SectionName;
protected override void ValidateOptions(MyConnectorOptions options, ValidationContext context)
{
context
.RequireNotEmpty(options.BaseUrl, nameof(options.BaseUrl))
.RequirePositive(options.TimeoutSeconds, nameof(options.TimeoutSeconds))
.RequireInRange(options.MaxRetries, nameof(options.MaxRetries), 0, 10);
// Validate URL format
if (!string.IsNullOrWhiteSpace(options.BaseUrl) &&
!Uri.TryCreate(options.BaseUrl, UriKind.Absolute, out _))
{
context.AddError(nameof(options.BaseUrl), "must be a valid absolute URL.");
}
}
}

View File

@@ -0,0 +1,33 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using StellaOps.Plugin;
using StellaOps.Plugin.Versioning;
// Declare plugin version for compatibility checking
[assembly: StellaPluginVersion("1.0.0-template", MinimumHostVersion = "1.0.0")]
namespace StellaOps.Plugin.MyConnector;
/// <summary>
/// Plugin entry point for the MyConnector connector.
/// </summary>
public sealed class MyConnectorPlugin : IConnectorPlugin
{
/// <inheritdoc />
public string Name => "MyConnector";
/// <inheritdoc />
public bool IsAvailable(IServiceProvider services)
{
// Add availability checks here (e.g., required configuration present)
return true;
}
/// <inheritdoc />
public IFeedConnector Create(IServiceProvider services)
{
var logger = services.GetRequiredService<ILogger<MyConnector>>();
var options = services.GetRequiredService<Microsoft.Extensions.Options.IOptions<MyConnectorOptions>>();
return new MyConnector(logger, options.Value);
}
}

View File

@@ -0,0 +1,47 @@
# MyConnector Plugin
A StellaOps connector plugin template.
## Getting Started
1. Update the `MyConnectorOptions.cs` with your connector-specific configuration
2. Implement the fetch, parse, and map logic in `MyConnector.cs`
3. Update the plugin metadata in `MyConnectorPlugin.cs`
4. Build and sign your plugin
## Configuration
Add the following to your `appsettings.json`:
```json
{
"Plugins": {
"MyConnector": {
"BaseUrl": "https://api.example.com",
"ApiKey": "your-api-key",
"TimeoutSeconds": 30,
"MaxRetries": 3
}
}
}
```
## Building
```bash
dotnet build -c Release
```
## Signing
For production use, sign your plugin with Cosign:
```bash
cosign sign --key $COSIGN_KEY bin/Release/net10.0/StellaOps.Plugin.MyConnector.dll
```
## Testing
```bash
dotnet test
```

View File

@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Version>1.0.0-template</Version>
<Description>StellaOps MyConnector Plugin</Description>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
</ItemGroup>
<!-- Reference StellaOps plugin infrastructure -->
<!-- Adjust paths based on your repository structure -->
<ItemGroup>
<ProjectReference Include="..\..\src\__Libraries\StellaOps.Plugin\StellaOps.Plugin.csproj" />
<ProjectReference Include="..\..\src\__Libraries\StellaOps.DependencyInjection\StellaOps.DependencyInjection.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,54 @@
{
"$schema": "http://json.schemastore.org/template",
"author": "StellaOps",
"classifications": ["StellaOps", "Plugin", "Scheduler", "Job"],
"identity": "StellaOps.Plugin.Scheduler",
"name": "StellaOps Scheduled Job Plugin",
"shortName": "stellaops-plugin-scheduler",
"description": "A template for creating StellaOps scheduled job plugins",
"tags": {
"language": "C#",
"type": "project"
},
"sourceName": "MyJob",
"preferNameDirectory": true,
"symbols": {
"jobName": {
"type": "parameter",
"datatype": "text",
"description": "The name of the scheduled job (e.g., 'Cleanup' for CleanupJob)",
"defaultValue": "MyJob",
"replaces": "MyJob",
"fileRename": "MyJob"
},
"cronSchedule": {
"type": "parameter",
"datatype": "text",
"description": "Default cron schedule for the job",
"defaultValue": "0 0 * * *",
"replaces": "0 0 * * *"
},
"namespace": {
"type": "parameter",
"datatype": "text",
"description": "The root namespace for the plugin",
"defaultValue": "StellaOps.Plugin.MyJob",
"replaces": "StellaOps.Plugin.MyJob"
},
"pluginVersion": {
"type": "parameter",
"datatype": "text",
"description": "The initial plugin version",
"defaultValue": "1.0.0",
"replaces": "1.0.0-template"
}
},
"postActions": [
{
"description": "Restore NuGet packages required by this project",
"manualInstructions": [{ "text": "Run 'dotnet restore'" }],
"actionId": "210D431B-A78B-4D2F-B762-4ED3E3EA9025",
"continueOnError": true
}
]
}

View File

@@ -0,0 +1,25 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using StellaOps.DependencyInjection;
using StellaOps.DependencyInjection.Validation;
namespace StellaOps.Plugin.MyJob;
/// <summary>
/// Registers MyJob services with the dependency injection container.
/// </summary>
public sealed class MyJobDependencyInjectionRoutine : IDependencyInjectionRoutine
{
/// <inheritdoc />
public IServiceCollection Register(IServiceCollection services, IConfiguration configuration)
{
// Register options with fail-fast validation
services.AddOptionsWithValidation<MyJobOptions, MyJobOptionsValidator>(
MyJobOptions.SectionName);
// Register the scheduled job
services.AddSingleton<IScheduledJob, MyJob>();
return services;
}
}

View File

@@ -0,0 +1,105 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Plugin.Versioning;
// Declare plugin version for compatibility checking
[assembly: StellaPluginVersion("1.0.0-template", MinimumHostVersion = "1.0.0")]
namespace StellaOps.Plugin.MyJob;
/// <summary>
/// A scheduled job that runs on a configurable cron schedule.
/// </summary>
public sealed class MyJob : IScheduledJob
{
private readonly ILogger<MyJob> _logger;
private readonly MyJobOptions _options;
public MyJob(ILogger<MyJob> logger, IOptions<MyJobOptions> options)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_options = options?.Value ?? throw new ArgumentNullException(nameof(options));
}
/// <inheritdoc />
public string JobId => "my-job";
/// <inheritdoc />
public string DisplayName => "My Scheduled Job";
/// <inheritdoc />
public string CronSchedule => _options.CronSchedule;
/// <inheritdoc />
public async Task<JobResult> ExecuteAsync(JobContext context, CancellationToken cancellationToken = default)
{
_logger.LogInformation("Starting scheduled job {JobId} at {StartTime}", JobId, context.ScheduledTime);
try
{
// TODO: Implement your job logic here
await Task.Delay(100, cancellationToken);
_logger.LogInformation("Completed scheduled job {JobId}", JobId);
return new JobResult(
Success: true,
Message: "Job completed successfully",
ItemsProcessed: 0,
Errors: Array.Empty<string>());
}
catch (Exception ex) when (ex is not OperationCanceledException)
{
_logger.LogError(ex, "Failed to execute scheduled job {JobId}", JobId);
return new JobResult(
Success: false,
Message: ex.Message,
ItemsProcessed: 0,
Errors: new[] { ex.Message });
}
}
}
/// <summary>
/// Represents a scheduled job that can be executed by the scheduler.
/// </summary>
public interface IScheduledJob
{
/// <summary>
/// Gets the unique identifier for this job.
/// </summary>
string JobId { get; }
/// <summary>
/// Gets the display name for this job.
/// </summary>
string DisplayName { get; }
/// <summary>
/// Gets the cron schedule expression for this job.
/// </summary>
string CronSchedule { get; }
/// <summary>
/// Executes the scheduled job.
/// </summary>
Task<JobResult> ExecuteAsync(JobContext context, CancellationToken cancellationToken = default);
}
/// <summary>
/// Context for job execution.
/// </summary>
public sealed record JobContext(
DateTimeOffset ScheduledTime,
DateTimeOffset ActualStartTime,
string? CorrelationId = null);
/// <summary>
/// Result of a job execution.
/// </summary>
public sealed record JobResult(
bool Success,
string? Message,
int ItemsProcessed,
IReadOnlyList<string> Errors);

View File

@@ -0,0 +1,43 @@
using System.ComponentModel.DataAnnotations;
namespace StellaOps.Plugin.MyJob;
/// <summary>
/// Configuration options for the MyJob scheduled plugin.
/// </summary>
public sealed class MyJobOptions
{
/// <summary>
/// The configuration section name.
/// </summary>
public const string SectionName = "Plugins:MyJob";
/// <summary>
/// Gets or sets the cron schedule expression.
/// Default: "0 0 * * *" (daily at midnight).
/// </summary>
[Required]
public string CronSchedule { get; set; } = "0 0 * * *";
/// <summary>
/// Gets or sets whether the job is enabled.
/// </summary>
public bool Enabled { get; set; } = true;
/// <summary>
/// Gets or sets the maximum execution time in minutes.
/// </summary>
[Range(1, 1440)]
public int MaxExecutionMinutes { get; set; } = 60;
/// <summary>
/// Gets or sets the batch size for processing items.
/// </summary>
[Range(1, 10000)]
public int BatchSize { get; set; } = 100;
/// <summary>
/// Gets or sets whether to continue processing on error.
/// </summary>
public bool ContinueOnError { get; set; }
}

View File

@@ -0,0 +1,31 @@
using StellaOps.DependencyInjection.Validation;
namespace StellaOps.Plugin.MyJob;
/// <summary>
/// Validates <see cref="MyJobOptions"/> configuration.
/// </summary>
public sealed class MyJobOptionsValidator : OptionsValidatorBase<MyJobOptions>
{
protected override string SectionPrefix => MyJobOptions.SectionName;
protected override void ValidateOptions(MyJobOptions options, ValidationContext context)
{
context
.RequireNotEmpty(options.CronSchedule, nameof(options.CronSchedule))
.RequirePositive(options.MaxExecutionMinutes, nameof(options.MaxExecutionMinutes))
.RequirePositive(options.BatchSize, nameof(options.BatchSize))
.RequireInRange(options.MaxExecutionMinutes, nameof(options.MaxExecutionMinutes), 1, 1440)
.RequireInRange(options.BatchSize, nameof(options.BatchSize), 1, 10000);
// Validate cron expression format (basic check)
if (!string.IsNullOrWhiteSpace(options.CronSchedule))
{
var parts = options.CronSchedule.Split(' ', StringSplitOptions.RemoveEmptyEntries);
if (parts.Length < 5 || parts.Length > 6)
{
context.AddError(nameof(options.CronSchedule), "must be a valid cron expression with 5 or 6 fields.");
}
}
}
}

View File

@@ -0,0 +1,54 @@
# MyJob Scheduled Plugin
A StellaOps scheduled job plugin template.
## Getting Started
1. Update the `MyJobOptions.cs` with your job-specific configuration
2. Implement the job logic in `MyJob.cs`
3. Build and sign your plugin
## Configuration
Add the following to your `appsettings.json`:
```json
{
"Plugins": {
"MyJob": {
"Enabled": true,
"CronSchedule": "0 0 * * *",
"MaxExecutionMinutes": 60,
"BatchSize": 100,
"ContinueOnError": false
}
}
}
```
### Cron Schedule Examples
- `0 0 * * *` - Daily at midnight
- `0 */6 * * *` - Every 6 hours
- `0 0 * * 0` - Weekly on Sunday at midnight
- `0 0 1 * *` - Monthly on the 1st at midnight
## Building
```bash
dotnet build -c Release
```
## Signing
For production use, sign your plugin with Cosign:
```bash
cosign sign --key $COSIGN_KEY bin/Release/net10.0/StellaOps.Plugin.MyJob.dll
```
## Testing
```bash
dotnet test
```

View File

@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Version>1.0.0-template</Version>
<Description>StellaOps MyJob Scheduled Plugin</Description>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
</ItemGroup>
<!-- Reference StellaOps plugin infrastructure -->
<!-- Adjust paths based on your repository structure -->
<ItemGroup>
<ProjectReference Include="..\..\src\__Libraries\StellaOps.Plugin\StellaOps.Plugin.csproj" />
<ProjectReference Include="..\..\src\__Libraries\StellaOps.DependencyInjection\StellaOps.DependencyInjection.csproj" />
</ItemGroup>
</Project>