Achievehaus.Plugin
0.3.7-preview.1
dotnet add package Achievehaus.Plugin --version 0.3.7-preview.1
NuGet\Install-Package Achievehaus.Plugin -Version 0.3.7-preview.1
<PackageReference Include="Achievehaus.Plugin" Version="0.3.7-preview.1" />
<PackageVersion Include="Achievehaus.Plugin" Version="0.3.7-preview.1" />
<PackageReference Include="Achievehaus.Plugin" />
paket add Achievehaus.Plugin --version 0.3.7-preview.1
#r "nuget: Achievehaus.Plugin, 0.3.7-preview.1"
#:package Achievehaus.Plugin@0.3.7-preview.1
#addin nuget:?package=Achievehaus.Plugin&version=0.3.7-preview.1&prerelease
#tool nuget:?package=Achievehaus.Plugin&version=0.3.7-preview.1&prerelease
Achievehaus.Plugin
Write Achievehaus plugins in C#.
One class is the plugin: mark it [Plugin], derive it from PluginBase, and
override the methods of the roles it plays:
| role | override | source.json declares it with |
|---|---|---|
| detector | Check(ClipEvents events) |
a method without a template |
| screen | Seen(Sighting sighting, ClipEvents events) |
watches (its method names the moments) |
| action | Outcome Run(string action, Clip clip) |
actions |
| resolver | Discovery Discover(IReadOnlyList<string> followed) |
a resolver |
| page | PageData View(string page, Span? scope), PageData Act(string page, string action) |
pages |
using Achievehaus;
[Plugin]
public sealed class Unlocks : PluginBase
{
LogTail log = null!;
public override void Start(Settings settings, Context context) =>
log = LogTail.Open(settings.GetString("log_path"));
public override void Check(ClipEvents events)
{
foreach (var line in log.NewLines())
if (line.StartsWith("Achievement unlocked: "))
events.Emit(new ClipEvent().Var("name", line[22..]));
}
}
Start a project with the plugin kit:
achievehaus-plugin new my-plugin --lang csharp
cd my-plugin && achievehaus-plugin run
Roles. source.json says which roles the source plays, and the app runs
each in an instance of its own: a new object of your class, started once with
Start (context.Role says which). A role you do not override answers
not-implemented. The build says so first: no [Plugin] class, two of them,
or one the app cannot make (abstract, generic, no public constructor taking
nothing) is a compile error (AH0001--AH0003); one that overrides no role
is a warning (AH0004); a role source.json declares that the class does not
override is an error (AH0005), and one the class overrides that source.json
does not declare a warning (AH0006). The instances share nothing at run time
but the settings and the folders: what one role works out for another goes in
State.
- Screen. A
Sightinghas the watch's id, when it was seen, a match'sScoreandExemplar, the OCR'sLines(each with its box as fractions of the region) and theCrop's pixels.Read.Lines("killer", "victim").Drop("KILL FEED")(orRead.Regions().Region(...)) turns the lines into vars:Vars(lines),Map(lines). Its events are live; it has no network. A C# screen plugin does not load yet: .NET's start needs ~52M fuel and the role'sconfiguregets 50M (#346). - Action. A
Cliphas its title, source, kind, vars (when this plugin saved it), game,At,Duration,MomentOffsetandFiles-- the video first,Video-- as read handles:Read(offset, len)(4 MiB at most) orOpenRead(), a seekableStreaman upload sends asRequest.BodyStream. AnswerOutcome.Done(link),Outcome.Trim(from, to)orOutcome.Skipped; throw for a failure.Secrets.Set(token)keeps the plugin's own sign-in (asecretneed with"writable": true),Set("")signs out. - Resolver.
new Discovery(installs),.Add(install),.Following(dir)(a second library: the app grants it and calls again with it infollowed),.Client("MyLauncher")(the launcher's own program, never the game). - Page.
new PageData().Vars("tonight", new Vars().Var("matches", 3)).Rows("recent", rows);Actis the page again by default.Pages.Changed("matches"), from any role, has the app askViewagain while the page is on screen.
What every role may ask the app:
Http.Get, Http.Post, Http.Send |
requests over wasi:http, to the hosts source.json grants ({ "wasi": "http", "authority": [...] }); Http.GetJson is null for any failure |
Status.Report, Status.Clear, Notes.Show |
status notes: what the plugin is waiting for, beside the session it is about; Because, Steps, Brings, Expected, Setting say its detail |
State.ReadJson, WriteJson, ReadText, WriteText, Path |
the plugin's state folder (/state), 8 MiB, every role's; a write goes to {name}.partial and is renamed into place |
Registry.ReadString, ReadU32, Subkeys |
the Windows registry, read only, under the keys source.json grants |
LogTail, Seen, Every |
following a log, keeping only what is new, doing something less often |
System.Net.Http does not reach the app under componentize-dotnet, which is
why Http is the SDK's own: one request at a time, blocking until the whole
answer is in, a body's content-length sent for it. JSON in State goes
through JsonNode, or a JsonTypeInfo<T> from a JsonSerializerContext:
the component is trimmed, and System.Text.Json's reflection is off in it.
What the plugin writes to Console.Out or Console.Error is its log.
The package brings componentize-dotnet, the app's world and the generator that
finds the [Plugin] class; a plugin project holds no WIT and no generated
code, and the component keeps nothing of the SDK it does not call (no
reflection: the generator settles which class and which roles when it
compiles, and an export of a role not played compiles to not-implemented
alone). A resolver is about 3 MiB, a detector about 4, a plugin playing every
role about 7.
Testing. Everything but the app's imports runs natively, so a plugin's
own tests call its methods: new Sighting("feed").WithLines("Ana", "Bo"),
ClipFile.FromBytes(...) in a new Clip(...), new Settings(json).
Outside a component the registry finds nothing, notes go nowhere, HTTP
throws, State is $ACHIEVEHAUS_STATE_DIR (else achievehaus-state in the
temporary folder), and Pages.TakeChanged() and Secrets.TakeSet() hand
back what was said.
Platforms. The component is built by NativeAOT-LLVM, which runs on Windows and Linux. On macOS the plugin compiles and type-checks, and the build then stops with an explanation: NativeAOT-LLVM has no macOS build yet (dotnet/runtimelab#2119). The component itself runs on every platform the app does.
Its tests. dotnet test Achievehaus.Plugin.Tests (from sdk/csharp) runs
everything that works outside a component, on any OS, and the generator over
sample plugins; .github/workflows/plugin-kit.yml builds every C# template
and examples/ (a plugin per role the templates do not cover, and one
playing every role) into a component and runs it in the app's sandbox.
Learn more about Target Frameworks and .NET Standard.
-
net10.0
- BytecodeAlliance.Componentize.DotNet.Wasm.SDK (>= 0.8.0-preview00011)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.3.7-preview.1 | 47 | 11/8/2026 |
| 0.3.6-preview.1 | 64 | 11/5/2026 |
| 0.3.5-preview.1 | 63 | 11/4/2026 |
| 0.3.4-preview.1 | 55 | 11/4/2026 |
| 0.3.3-preview.1 | 68 | 10/31/2026 |
| 0.3.2-preview.1 | 74 | 10/30/2026 |
| 0.3.1-preview.1 | 63 | 10/30/2026 |
| 0.3.0-preview.1 | 59 | 10/30/2026 |