Build and distribute C# libraries and nodes for vvvv gamma.
Install
mkdir -p .claude/skills/vvvv-node-libraries && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/16219" && unzip -o skill.zip -d .claude/skills/vvvv-node-libraries && rm skill.zipInstalls to .claude/skills/vvvv-node-libraries
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
Helps set up C# library projects that provide nodes to vvvv gamma — project directory structure, Initialization.cs with AssemblyInitializer, service registration via RegisterService, IResourceProvider factories, ImportAsIs / ImportNamespace / ImportType selection, category organization, .csproj setup, and dynamic node factories via RegisterNodeFactory. Also covers contributing changes to an existing/upstream vvvv library — fork → branch → PR workflow, editable source packages (--package-repositories / --editable-packages), and the .vl-document diff problem. Use when creating a new vvvv library, VL package, NuGet package for vvvv, deciding which import attribute to use, organizing categories, controlling which public types become nodes, registering services or node factories, setting up the project structure, or contributing a fix/feature/PR to a library you don't own (e.g. VL.StandardLibs). Trigger when the user says 'create a package', 'make a library', 'distribute nodes', 'organize categories', 'hide internal helpers from the node browser', 'publish a VL package', 'contribute to vvvv', 'file a PR on a vvvv library', or 'edit an existing library'.Key capabilities
- →Set up C# library projects for vvvv gamma nodes
- →Register services via `RegisterService` in `Initialization.cs`
- →Define dynamic node factories via `RegisterNodeFactory`
- →Organize categories for nodes in the node browser
- →Control which public types become nodes using import attributes
How it works
The skill guides the setup of C# library projects for vvvv gamma by defining directory structure, `Initialization.cs` for assembly initialization and service registration, and using import attributes to control node visibility.
Inputs & outputs
When to use vvvv-node-libraries
- →Create vvvv gamma package
- →Register service in vvvv
- →Define custom nodes
- →Publish vl package
About this skill
Creating vvvv gamma Node Libraries
A node library is a project that provides multiple nodes to vvvv gamma as a distributable package. This skill covers the project-level concerns: directory structure, naming conventions, category organization, service registration, and node factories.
For writing individual node classes (ProcessNode, Update, pins, change detection), see vvvv-custom-nodes. For consuming services inside node constructors (IFrameClock, Game, logging), see vvvv-custom-nodes/services.md.
Creating your own library vs. contributing to one you don't own are different tasks. This SKILL.md and its design/publishing references cover creating and distributing a package. To change or submit a PR to an existing/upstream library (fork → branch → PR workflow, editable source packages, the .vl diff problem), see contributing.md.
Library Recognition Pattern
vvvv recognizes a directory as a library when the folder name, .vl file, and .nuspec all share the same name:
VL.MyLibrary/ # Folder name = package name
├── VL.MyLibrary.vl # .vl document — MUST match folder name
├── VL.MyLibrary.nuspec # NuGet spec — MUST match folder name
├── lib/
│ └── net8.0/ # Compiled DLLs go here
│ └── VL.MyLibrary.dll
├── src/
│ ├── Initialization.cs # [assembly:] attributes + AssemblyInitializer
│ ├── Nodes/
│ │ ├── MyProcessNode.cs # [ProcessNode] classes
│ │ └── MyOperations.cs # Static methods (stateless nodes)
│ ├── Services/
│ │ └── MyService.cs # Per-app singletons
│ └── VL.MyLibrary.csproj
├── shaders/ # Optional: SDSL shaders (auto-discovered)
│ └── MyEffect_TextureFX.sdsl
└── help/ # Optional: .vl help patches
└── HowTo Use MyNode.vl
Critical conventions:
- Folder name,
.vlfile, and.nuspecmust be identical (e.g., allVL.MyLibrary) - The
.csprojmust output DLLs tolib/net8.0/relative to the package root - No
.vlfile within a package should reference a.csproj— this forces the package into editable mode - The library directory must be in a configured package-repository directory for vvvv to find it
.csproj Output Path
The .csproj must compile into the library's lib/net8.0/ folder:
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<OutputPath>..\..\lib\net8.0\</OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
</PropertyGroup>
What gets imported as a node — the foundational rule
A type becomes a node in vvvv's node browser when two conditions are both true:
- The type is
public(and lives in an imported assembly). - The type's C# namespace is covered by an
[assembly: ImportAsIs]/[assembly: ImportNamespace]declaration, OR the type is listed by an[assembly: ImportType]declaration.
If either condition is false, the type is invisible to vvvv. Importing is opt-in by namespace, not by type accessibility alone. A public class in a namespace nobody imports is just as hidden from the node browser as an internal class.
When a type IS imported, vvvv generates nodes from its full public surface:
- Public classes and structs → constructor + public methods/properties become nodes
- Public static methods → operation nodes
- Public enums → split + values become nodes
- Public records and interfaces → handled like classes
[ProcessNode] does NOT gate node visibility. It is purely lifecycle sugar — it tells vvvv "this is a stateful class with an Update() method, manage one instance per node, call Update() each frame". A plain public class Foo { public Foo() {} public int Bar(int x) => x; } in an imported namespace becomes a node browser entry exactly the same as one decorated with [ProcessNode]. The attribute affects how the node is invoked, not whether it appears.
Implication for library design: the primary lever for "what shows up in the node browser" is which namespaces you import, not which types are public. But note that importing is recursive — declaring one root namespace pulls in every namespace nested below it, so a .Internal sub-namespace is not a hiding place. See Excluding helpers from the node browser for the four levers that actually work.
Source: VL.StandardLibs ImportAsIsAttribute, Gray Book — Writing nodes using C#.
Initialization.cs — The Entry Point
Every node library needs assembly-level attributes. Combine in one file:
using VL.Core;
using VL.Core.CompilerServices;
using VL.Core.Import;
// Required: tells vvvv to scan this assembly for nodes
[assembly: ImportAsIs(Namespace = "MyCompany.MyLibrary", Category = "MyLibrary")]
// Optional: register services before any node runs
[assembly: AssemblyInitializer(typeof(MyCompany.MyLibrary.Initialization))]
namespace MyCompany.MyLibrary;
public sealed class Initialization : AssemblyInitializer<Initialization>
{
public override void Configure(AppHost appHost)
{
var services = appHost.Services;
// Register per-app singletons (created lazily on first access)
services.RegisterService<MyService>(serviceProvider =>
{
return new MyService(serviceProvider);
});
}
}
Choosing the right import attribute
vvvv provides three assembly-level attributes for declaring what becomes a node. Pick based on how much control you need.
[assembly: ImportAsIs] — single, namespace-rooted
[assembly: ImportAsIs(Namespace = "VL.MyLib", Category = "MyLib")]
| Property | Behaviour |
|---|---|
AllowMultiple | false — at most ONE per assembly |
| Scope | All public types in Namespace (and its children) |
| Category | Category parameter is the root; sub-namespaces below Namespace extend it |
Both parameters are optional; all four combinations are legal and each does something different:
[assembly: ImportAsIs] // the scaffolded default:
// nothing stripped, no root →
// VL.MyLib.Particles ⇒ "VL.MyLib.Particles"
[assembly: ImportAsIs(Namespace = "VL.MyLib")] // strip only →
// VL.MyLib.Particles ⇒ "Particles" (top level!)
[assembly: ImportAsIs(Category = "MyLib")] // root only →
// VL.MyLib.Particles ⇒ "MyLib.VL.MyLib.Particles"
[assembly: ImportAsIs(Namespace = "VL.MyLib", Category = "MyLib")] // ✅ strip + root →
// VL.MyLib.Particles ⇒ "MyLib.Particles"
The last form is what you almost always want. Use ImportAsIs when the whole library lives
under one root namespace and you want one root category. You cannot stack two ImportAsIs
to split sub-namespaces into different categories — it is AllowMultiple = false.
[assembly: ImportNamespace] — per-namespace, multi-use
// ⚠️ Order matters — first declaration wins. Specific before general.
[assembly: ImportNamespace("VL.MyLib.Renderers", Category = "MyLib.Rendering")]
[assembly: ImportNamespace("VL.MyLib.Resources", Category = "MyLib.Resources")]
[assembly: ImportNamespace("VL.MyLib.Experimental", Category = "MyLib.Experimental")]
[assembly: ImportNamespace("VL.MyLib", Category = "MyLib")] // catch-all, LAST
| Property | Behaviour |
|---|---|
AllowMultiple | true — declare as many as you need |
| Scope | Public types in that namespace and every namespace nested below it (recursive) |
| Resolution | First declaration wins, not longest prefix — declare specific before general |
Use when one library has multiple sub-namespaces and you want each to land in a distinct category — without polluting the browser with C# folder names. This is the right tool for multi-category libraries.
[assembly: ImportType] — per-type, hand-picked
[assembly: ImportType(typeof(MyRenderer), Category = "MyLib.Rendering")]
[assembly: ImportType(typeof(MyResource), Category = "MyLib.Resources", Name = "Resource")]
| Property | Behaviour |
|---|---|
AllowMultiple | true — declare as many as you need |
| Scope | Only the listed types — nothing else from the assembly is auto-imported |
| Use with | Either alone (no ImportAsIs/ImportNamespace) for closed-list libraries, or alongside the namespace attributes to override category/name for specific types |
Use for surgical control — e.g. when you want to expose only a curated subset of a large internal codebase, or to force one outlier into a different category than its namespace siblings.
Decision matrix
| Library shape | Recommended attribute(s) |
|---|---|
| One namespace, one category, all public types are intentional | [assembly: ImportAsIs(Namespace, Category)] |
| One library, several distinct sub-categories | One [assembly: ImportNamespace] per sub-namespace |
| Curated set of nodes, lots of public helpers you don't want exposed | [assembly: ImportType] per node, no ImportAsIs |
| Mostly auto-imported, a few outliers | [assembly: ImportAsIs] + [assembly: ImportType] overrides |
🛑 Importing is RECURSIVE — a nested namespace is NOT a hiding place
This is the single most common way a library's node browser gets polluted, and the mistake is easy to make because the intent reads as obviously correct.
ImportFromNamespace in [AssemblySy
Content truncated.
When not to use it
- →For writing individual node classes (ProcessNode, Update, pins, change detection)
- →For consuming services inside node constructors (IFrameClock, Game, logging)
- →For contributing changes to an existing/upstream vvvv library via fork/branch/PR workflow
Limitations
- →Does not cover writing individual node classes
- →Does not cover consuming services inside node constructors
- →Does not cover contributing to existing upstream libraries
How it compares
This skill provides specific project-level conventions and code examples for creating vvvv gamma node libraries, including service registration and node factory setup, which differs from general C# library development.
Compared to similar skills
vvvv-node-libraries side by side with the closest alternatives in the catalog.
| Skill | Installs | Updated | Safety | Difficulty |
|---|---|---|---|---|
| vvvv-node-libraries (this skill) | 0 | 2mo | No flags | Advanced |
| script-execute | 0 | 3mo | Review | Intermediate |
| csharp-developer | 43 | 3mo | No flags | Advanced |
| csharp-pro | 9 | 4mo | No flags | Intermediate |
Try saying
Example prompts that trigger this skill in your AI assistant.
More by tebjan
View all by tebjan →You might also like
script-execute
Yunbada
Compiles and executes C# code dynamically using Roslyn. Supports two modes: full code mode (default) requires a complete class definition, while body-only mode (isMethodBody=true) auto-generates the boilerplate so you only provide the method body. Unity objects (GameObject, Component, etc.) can be p
csharp-developer
zenobi-us
Expert C# developer specializing in modern .NET development, ASP.NET Core, and cloud-native applications. Masters C# 12 features, Blazor, and cross-platform development with emphasis on performance and clean architecture.
csharp-pro
sickn33
Write modern C# code with advanced features like records, pattern matching, and async/await. Optimizes .NET applications, implements enterprise patterns, and ensures comprehensive testing. Use PROACTIVELY for C# refactoring, performance optimization, or complex .NET solutions.
dotnet-backend-patterns
wshobson
Master C#/.NET backend development patterns for building robust APIs, MCP servers, and enterprise applications. Covers async/await, dependency injection, Entity Framework Core, Dapper, configuration, caching, and testing with xUnit. Use when developing .NET backends, reviewing C# code, or designing API architectures.
azure-servicebus-dotnet
microsoft
Azure Service Bus SDK for .NET. Enterprise messaging with queues, topics, subscriptions, and sessions. Use for reliable message delivery, pub/sub patterns, dead letter handling, and background processing. Triggers: "Service Bus", "ServiceBusClient", "ServiceBusSender", "ServiceBusReceiver", "ServiceBusProcessor", "message queue", "pub/sub .NET", "dead letter queue".
backend-testing
exceptionless
Backend testing with xUnit, Foundatio.Xunit, integration tests with AppWebHostFactory, FluentClient, ProxyTimeProvider for time manipulation, and test data builders. Keywords: xUnit, Fact, Theory, integration tests, AppWebHostFactory, FluentClient, ProxyTimeProvider, TimeProvider, Foundatio.Xunit, TestWithLoggingBase, test data builders