datalust/seqcli/building-seq-plug-in-apps

Claude Code and agent skill by datalust/seqcli

Trust tier 2
  • community
LLM review pending
Source check pending

Checking that the SKILL.md file is still in the repository.

Use this when developing plug-in Seq inputs (for ingestion) and outputs (for alert notifications or streamed events).

Install

npx vskill@latest install datalust/seqcli/building-seq-plug-in-apps

SKILL.md

This skill covers building Seq apps (plugins) using the Seq.Apps runtime API. Seq apps are .NET libraries that extend Seq with custom event processing (output apps) or event generation (input apps).

Project structure

A Seq app follows this layout:

src/
Seq.App.{Name}/
Seq.App.{Name}.csproj # Main library
{Name}App.cs # SeqApp subclass (entry point)
... # Supporting types
Resources/ # Embedded resources (default templates, etc.)
Seq.App.{Name}.SmokeTest/ # Optional console app for manual testing
Program.cs
test/
Seq.App.{Name}.Tests/
{Name}AppTests.cs # App-level integration tests
...Tests.cs # Unit tests for components
Support/
Test{Gateway}.cs # Test doubles
Some.cs # LogEvent factory helpers

Input apps use Seq.Input.{Name} naming.

.csproj conventions
xml
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Seq.Apps" Version="..." />
<!-- Add Seq.Syntax (project or package reference) if using the Seq template language -->
</ItemGroup>
<ItemGroup>
<!-- Package dependencies into primary NUPKG file, except those shipped in the app host itself -->
<None Include="./obj/publish/**/*" Exclude="./obj/publish/$(MSBuildProjectName).dll;./obj/publish/Seq.Apps.dll;./obj/publish/Serilog.dll" Pack="true" PackagePath="lib/$(TargetFramework)" />
</ItemGroup>
</Project>

Embedded resources for default templates use LogicalName for clean resource stream access:

xml
<ItemGroup>
<EmbeddedResource Include="./Resources/DefaultHtmlTemplate.txt" LogicalName="DefaultHtmlTemplate" />
</ItemGroup>

Test projects target the latest stable .NET release, and use xUnit.

The runtime pins its own copies of Seq.Apps, Serilog, and Seq.Syntax — the app's copies of these assemblies are ignored at load time. Other dependencies are loaded from the app's package.

Namespace disambiguation

Projects named Seq.App.{Name} have a root namespace that collides with Seq.Apps. Use global:: where Seq.Apps.App or Seq.Apps.Host is referenced as a type within a Seq.App.* namespace:

csharp
public static LogEventPropertyValue? MyAppHost(global::Seq.Apps.Host host) { ... }
public static LogEventPropertyValue? MyAppInstance(global::Seq.Apps.App app) { ... }

Use InternalsVisibleTo in the main project to expose internal constructors and types to the test project.

App class structure

Output apps (event subscribers)

Output apps derive from SeqApp and implement ISubscribeToAsync<LogEvent>:

csharp
using Seq.Apps;
using Serilog.Events;
[SeqApp("App Name",
Description = "A short sentence describing what the app does.")]
public class MyApp : SeqApp, ISubscribeToAsync<LogEvent>, IDisposable
{
readonly IMyGateway _gateway;
MyMessageFactory? _messageFactory;
// Public parameterless constructor for production use
public MyApp() : this(new MyHttpGateway()) { }
// Internal constructor for testing (dependency injection)
internal MyApp(IMyGateway gateway) { _gateway = gateway; }
// Settings (see "App settings" section below)
protected override void OnAttached()
{
// Validate required settings, resolve defaults, create factories
}
public async Task OnAsync(Event<LogEvent> evt)
{
// Process the event: render message, send via gateway
}
public void Dispose()
{
(_gateway as IDisposable)?.Dispose();
}
}

The SeqApp attribute's Name parameter is the display name in Seq's UI. Keep it short (1-3 words). Description is a single sentence shown below the name.

Output apps using raw JSON (ISubscribeToJsonAsync)

Apps that don't need Serilog LogEvent deserialization can implement ISubscribeToJsonAsync instead. This receives each event as a raw CLEF JSON string, skipping LogEvent/MessageTemplate/LogEventProperty construction entirely:

csharp
[SeqApp("App Name",
Description = "A short sentence describing what the app does.")]
public class MyApp : SeqApp, ISubscribeToJsonAsync, IAsyncDisposable
{
public async Task OnAsync(string json)
{
// Parse and process the raw CLEF JSON
}
public async ValueTask DisposeAsync() { }
}

Prefer ISubscribeToJsonAsync for apps that forward or transform events without rendering human-readable output (exporters, relays, bridges), especially at high volume. It avoids the Serilog dependency entirely. The trade-off is that the Event<LogEvent> wrapper (Id, EventType, Timestamp) and Seq.Syntax template rendering are not available; the app must extract CLEF fields (@t, @l, @mt, @x, etc.) from the JSON directly.

Stick with ISubscribeToAsync<LogEvent> when the app renders human-readable output using Seq.Syntax templates (emails, chat messages, tickets) or inspects structured property values using the Serilog type system (ScalarValue, SequenceValue, StructureValue).

Input apps (event publishers)

Input apps implement IPublishJson:

csharp
[SeqApp("My Input",
Description = "Periodically does X and publishes metrics to Seq.")]
public class MyInput : SeqApp, IPublishJson, IDisposable
{
public void Start(TextWriter inputWriter)
{
// Begin producing events; write CLEF JSON lines to inputWriter
// Return immediately; use background tasks for ongoing work
}
public void Stop()
{
// Block until publishing has stopped
}
}

The app must synchronize writes to inputWriter so that events are not interleaved (e.g., use a lock when multiple background threads produce events).

Lifecycle
  1. Seq instantiates the app using the public parameterless constructor
  2. The runtime sets [SeqAppSetting] properties via reflection, matched by property name
  3. Attach() is called, making App, Host, and Log available
  4. OnAttached() runs — validate settings and initialize here
  5. For subscribers: OnAsync() is called for each matching event
  6. For publishers: Start() is called, then Stop() on shutdown
  7. Dispose() if implemented

Each app instance runs as a separate seqcli process. Initialization, event dispatch, and disposal are single-threaded. Events are dispatched one at a time — if OnAsync() is slow, backpressure is applied naturally. Apps may create their own background threads for ingestion.

App settings

Settings are public properties on the app class decorated with [SeqAppSetting]. They appear in Seq's UI for the user to configure. The runtime injects values by matching on the property name (not DisplayName), using Enum.Parse for enum types and Convert.ChangeType for everything else.

SeqAppSetting attribute properties
PropertyTypePurpose
DisplayNamestringUser-facing label in the UI. If omitted, the property name is used.
HelpTextstringDescriptive text shown to the user. Should explain the setting's purpose and give examples.
IsOptionalboolIf true, the user can leave the field blank. Non-optional settings that are missing cause a startup error before Attach() is called.
InputTypeSettingInputTypeControls the UI input widget: Text, LongText, Checkbox, Integer, Decimal, Password. If Unspecified, the runtime chooses based on the property type.
SyntaxstringSyntax highlighting in the UI. "template" for fields accepting Seq template expressions (in {braces}), "code" for structured values (JSON, headers). Omit for plain values.
IsInvocationParameterboolMarks settings that can be overridden per-invocation (e.g., destination address, channel).
Quality guidelines for settings

Display names should be concise, title-case-ish, and match how users think about the concept:

GoodAvoid
Bot tokenBotToken
Chat IDTelegram Chat Identifier
Silent notificationDisableNotification
Body is plain textBodyIsPlainText
Connection securityProtocolSecurity

Help text should:

  • Start with a clear description of the setting's purpose
  • Include concrete examples where helpful (e.g., format strings, URL patterns, expected values)
  • Mention defaults explicitly (e.g., "The default is Etc/UTC.", "The default is POST.")
  • Use backticks for code/values in help text
  • Note when template syntax is supported: "Template syntax is supported."
  • For password/token fields, explain how to obtain the value
csharp
[SeqAppSetting(
DisplayName = "Time zone",
IsOptional = true,
HelpText = "The IANA time zone name used when formatting dates and times " +
"(e.g. Australia/Brisbane). The default is Etc/UTC.")]
public string? TimeZoneName { get; set; }
[SeqAppSetting(
DisplayName = "Bot token",
InputType = SettingInputType.Password,
HelpText = "The bot's API token, in the format 123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11. " +
"You can create a bot and obtain a token via @BotFather on Telegram.")]
public string? BotToken { get; set; }
Enum settings

When a setting has a fixed set of valid values, use an enum type. The runtime presents enum settings as a dropdown and handles validation automatically. Apply [Description] to enum members to control the dropdown text when the member name doesn't read naturally:

csharp
using System.ComponentModel;
public enum OtlpExportProtocol
{
[Description("HTTP/Protobuf")]
HttpProtobuf,
[Description("gRPC")]
Grpc
}
[SeqAppSetting(
DisplayName = "Protocol",
HelpText = "The OTLP export protocol. The default is `HTTP/Protobuf`.",
IsOptional = true)]
public OtlpExportProtocol Protocol { get; set; } = OtlpExportProtocol.HttpProtobuf;
Setting validation

Validate required settings in OnAttached() and throw ArgumentException with a message that names the setting using its display name in backticks:

csharp
protected override void OnAttached()
{
var botToken = NormalizeOption(BotToken)
?? throw new ArgumentException("A `Bot token` must be supplied.");
var chatId = NormalizeOption(ChatId)
?? throw new ArgumentException("A `Chat ID` must be supplied.");
// ...
}
// Treat empty strings as null for optional settings
static string? NormalizeOption(string? s) => s == "" ? null : s;

Gateway pattern (testability)

External I/O (HTTP APIs, SMTP, cloud services) is abstracted behind an interface so tests can run without network access.

csharp
// Interface
interface IMyGateway
{
Task<MyResponse> SendAsync(string param1, string param2, CancellationToken cancel);
}
// Production implementation
class MyHttpGateway : IMyGateway, IDisposable
{
readonly HttpClient _httpClient = new();
public async Task<MyResponse> SendAsync(...)
{
// HTTP calls, JSON serialization, response parsing
}
public void Dispose() => _httpClient.Dispose();
}
// Test double
class TestMyGateway : IMyGateway
{
public List<(string Param1, string Param2)> Sent { get; } = [];
public MyResponse NextResponse { get; set; } = new() { Ok = true };
public Task<MyResponse> SendAsync(string param1, string param2, CancellationToken cancel)
{
Sent.Add((param1, param2));
return Task.FromResult(NextResponse);
}
}

The app class has dual constructors:

csharp
public MyApp() : this(new MyHttpGateway()) { } // Production
internal MyApp(IMyGateway gateway) { _gateway = gateway; } // Testing

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 100 of 100Pattern checks on the skill's files.
  • Model reviewNot runA model review is scheduled for the next rescan.

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
168
Repository forks
30
7-day trend score
0

Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/datalust/seqcli/building-seq-plug-in-apps/badge)](https://verified-skill.com/skills/datalust/seqcli/building-seq-plug-in-apps)

Something wrong with this skill or its page? Tell us.