用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/tomevault-io/skills-registry --skill plugin-development命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
| Use when this capability is needed.
> Use when this capability is needed.
Review architecture and API design for the vfs-s3 project. Use when the user mentions @architect, asks to review an issue's design, discuss module boundaries, API shape, or architectural decisions for vfs-s3. Also trigger when the user wants to create an ADR (Architecture Decision Record) or evaluate a technical approach for the project. Intended for dispatch from Codex automation or Claude routines; GitHub trigger phrase: @vfs-s3-bot please prepare design doc Use when this capability is needed.
基于 SOC 职业分类
正在显示 SKILL.md
| name | plugin-development |
| description | | Use when this capability is needed. |
This skill provides patterns and requirements for implementing LCDPossible plugins.
LCDPossible uses a plugin-based architecture where each plugin:
src/Plugins/LCDPossible.Plugins.{Name}/IPanelPlugin interfaceIDisplayPanel implementationsplugin.json manifestsrc/Plugins/LCDPossible.Plugins.{Name}/
├── {Name}Plugin.cs # Main plugin class (IPanelPlugin)
├── plugin.json # Plugin manifest
├── LCDPossible.Plugins.{Name}.csproj
└── Panels/ # Panel implementations
├── {PanelName}Panel.cs
└── ...
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\LCDPossible.Sdk\LCDPossible.Sdk.csproj" />
</ItemGroup>
<!-- Copy plugin.json to output -->
<ItemGroup>
<None Update="plugin.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
{
"id": "lcdpossible.{name}",
"name": "Display Name",
"version": "1.0.0",
"author": "Author Name",
"description": "What this plugin provides",
"minimumSdkVersion": "1.0.0",
"assemblyName": "LCDPossible.Plugins.{Name}.dll",
"panelTypes": [
{
"typeId": "panel-id",
"displayName": "Panel Display Name",
"description": "What the panel shows",
"category": "Category Name",
"isLive": true
}
]
}
Panel Type Fields:
typeId: Unique identifier (lowercase, hyphens)displayName: Human-readable namedescription: Brief descriptioncategory: Grouping category (e.g., "System", "Screensaver", "Media")isLive: true if panel shows real-time dataprefixPattern (optional): For panels that accept parameters (e.g., "image:")using LCDPossible.Core.Plugins;
using LCDPossible.Core.Rendering;
using LCDPossible.Plugins.{Name}.Panels;
namespace LCDPossible.Plugins.{Name};
public sealed class {Name}Plugin : IPanelPlugin
{
public string PluginId => "lcdpossible.{name}";
public string DisplayName => "Plugin Display Name";
public Version Version => new(1, 0, 0);
public string Author => "Author Name";
public Version MinimumSdkVersion => new(1, 0, 0);
public IReadOnlyDictionary<string, PanelTypeInfo> PanelTypes { get; } =
new Dictionary<string, PanelTypeInfo>
{
["panel-id"] = new PanelTypeInfo
{
TypeId = "panel-id",
DisplayName = "Panel Name",
Description = "Description",
Category = "Category",
IsLive = true
}
};
public Task InitializeAsync(IPluginContext context, CancellationToken ct = default)
{
return Task.CompletedTask;
}
public IDisplayPanel? CreatePanel( panelTypeId, PanelCreationContext context)
{
typeId = panelTypeId.ToLowerInvariant();
IDisplayPanel? panel = typeId
{
=> PanelNamePanel(),
_ =>
};
(panel LCDPossible.Sdk.BaseLivePanel livePanel && context.ColorScheme != )
{
livePanel.SetColorScheme(context.ColorScheme);
}
panel;
}
{ }
}
Use BaseLivePanel from LCDPossible.Sdk for panels with:
using LCDPossible.Sdk;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Drawing;
using SixLabors.ImageSharp.Drawing.Processing;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
namespace LCDPossible.Plugins.{Name}.Panels;
public sealed class {PanelName}Panel : BaseLivePanel
{
public override string PanelId => "panel-id";
public override string DisplayName => "Panel Name";
public override bool IsLive => true; // Shows real-time data
public override bool IsAnimated => false; // Self-managed frame timing
public override Task InitializeAsync(CancellationToken ct = default)
{
// One-time setup
return Task.CompletedTask;
}
public override Task<Image<Rgba32>> RenderFrameAsync(
int width, int height, CancellationToken ct = default)
{
var image = new Image<Rgba32>(width, height, BackgroundColor);
image.Mutate(ctx =>
{
// Drawing code here
});
Task.FromResult(image);
}
}
| Property | Type | Description |
|---|---|---|
PanelId | string | Unique identifier matching plugin registration |
DisplayName | string | Human-readable name |
IsLive | bool | true if panel shows real-time/changing data |
IsAnimated | bool | true if panel manages its own animation timing |
// Colors from scheme
BackgroundColor, PrimaryTextColor, SecondaryTextColor, AccentColor
// Fonts (after InitializeAsync)
TitleFont, ValueFont, LabelFont, SmallFont
// Drawing helpers
DrawText(ctx, text, x, y, font, color, maxWidth);
DrawCenteredText(ctx, text, centerX, y, font, color);
DrawRightText(ctx, text, rightX, y, font, color);
DrawProgressBar(ctx, percentage, x, y, width, height, fillColor);
DrawVerticalBar(ctx, percentage, x, y, width, height, fillColor);
// Color helpers
GetUsageColor(percentage); // Green → Yellow → Red
GetTemperatureColor(celsius); // Blue → Green → Yellow → Red
For smooth, frame-rate independent animation:
private DateTime _lastUpdate = DateTime.UtcNow;
public override Task<Image<Rgba32>> RenderFrameAsync(...)
{
var now = DateTime.UtcNow;
var deltaTime = (float)(now - _lastUpdate).TotalSeconds;
_lastUpdate = now;
// Use deltaTime for movement
_position += _velocity * deltaTime;
}
For cyclical effects:
private DateTime _startTime = DateTime.UtcNow;
public override Task<Image<Rgba32>> RenderFrameAsync(...)
{
var time = (float)(DateTime.UtcNow - _startTime).TotalSeconds;
// Use time for oscillation
var pulse = MathF.Sin(time * 2f) * 0.5f + 0.5f;
}
// Ellipse/Circle
ctx.Fill(color, new EllipsePolygon(centerX, centerY, radiusX, radiusY));
ctx.Fill(color, new EllipsePolygon(centerX, centerY, radius)); // Circle
// Rectangle
ctx.Fill(color, new RectangleF(x, y, width, height));
// Line
ctx.DrawLine(color, thickness, new PointF(x1, y1), new PointF(x2, y2));
// Polygon path
var path = new PathBuilder();
path.MoveTo(new PointF(x1, y1)); // MUST start with MoveTo
path.LineTo(new PointF(x2, y2));
path.LineTo(new PointF(x3, y3));
path.CloseFigure();
ctx.Fill(color, path.Build());
For effects like plasma, fire, noise:
image.ProcessPixelRows(accessor =>
{
for (var y = 0; y < height; y++)
{
var row = accessor.GetRowSpan(y);
for (var x = 0; x < width; x++)
{
row[x] = new Rgba32(r, g, b);
}
}
});
| Category | Use For |
|---|---|
System | CPU, RAM, GPU, network info |
Screensaver | Animated visual effects |
Media | Images, videos, GIFs |
Web | HTML/website rendering |
Integration | External services (Proxmox, etc.) |
When adding a new panel:
{PanelName}Panel.cs in Panels/ folderPanelTypes dictionary in plugin classCreatePanel switch statementplugin.json panelTypes arrayProblem: Lines drawn from (0,0) to first point
Solution: Always call MoveTo() before LineTo()
// WRONG
path.LineTo(new PointF(x, y));
// CORRECT
path.MoveTo(new PointF(x, y));
path.LineTo(new PointF(x2, y2));
Problem: EllipsePolygon type not found
Solution: Add using directive:
using SixLabors.ImageSharp.Drawing;
Problem: Can't compare struct to null
Solution: Use .HasValue and .Value for nullable structs:
if (nullableStruct.HasValue)
{
var value = nullableStruct.Value;
}
public sealed class BouncingBallPanel : BaseLivePanel
{
private float _x, _y, _vx = 100, _vy = 80;
private DateTime _lastUpdate = DateTime.UtcNow;
public override string PanelId => "bouncing-ball";
public override string DisplayName => "Bouncing Ball";
public override bool IsAnimated => true;
public override Task<Image<Rgba32>> RenderFrameAsync(
int width, int height, CancellationToken ct = default)
{
var now = DateTime.UtcNow;
var dt = (float)(now - _lastUpdate).TotalSeconds;
_lastUpdate = now;
// Update position
_x += _vx * dt;
_y += _vy * dt;
// Bounce off walls
if (_x < 20 || _x > width - 20) _vx = -_vx;
if (_y < 20 || _y > height - 20) _vy = -_vy;
// Clamp
_x = Math.Clamp(_x, 20, width - 20);
_y = Math.Clamp(_y, 20, height - 20);
var image = new Image<Rgba32>(width, height, new Rgba32(0, , ));
image.Mutate(ctx =>
{
ctx.Fill( Rgba32(, , ),
EllipsePolygon(_x, _y, ));
});
Task.FromResult(image);
}
}
Converted and distributed by TomeVault — claim your Tome and manage your conversions.