Plugin Manifest and Packaging
Define a manifest v2 plugin JAR and distribute it as the release file.
Every Turboism plugin JAR contains exactly one descriptor at:
META-INF/turboism/plugin.jsonRuntime accepts schema version 2 only. Version 1 is not loaded or upgraded automatically.
Minimal manifest
{
"format": "turboism.plugin.meta",
"schemaVersion": 2,
"id": "dev.example.plugin.hello",
"name": "Hello Plugin",
"version": "0.1.0",
"description": "Minimal Turboism plugin lifecycle example.",
"entrypoints": [
"dev.example.plugin.HelloPlugin"
],
"turboismApi": "[0.1.0,0.2.0)",
"authors": [
{ "name": "Example Author" }
],
"license": "MIT",
"website": "https://example.org/hello-plugin",
"resources": [],
"i18n": {
"baseName": "META-INF/turboism/i18n/messages",
"locales": []
},
"dependencies": [],
"permissions": [],
"capabilities": [],
"environment": {
"requiresCubism": false,
"ui": "none"
}
}Entrypoint rules
Each entrypoint must:
- exist in the same plugin JAR;
- be public;
- implement
TurboismPluginor a subinterface; - expose a public no-argument constructor.
Multiple entrypoints share one plugin identity, descriptor, ClassLoader, PluginContext, DisposableScope, permission set, configuration namespace, storage namespace, resources, and localization declaration.
construct/init/enable: manifest order
disable/shutdown: reverse manifest orderA failure during construction, initialization, or enablement rolls back the entire JAR.
Dependencies
{
"id": "dev.example.plugin.base",
"version": "[1.0.0,2.0.0)",
"type": "required",
"ordering": "after",
"reason": "Uses the base plugin contract."
}typeisrequiredoroptional.orderingisnone,before, orafter.- dependency IDs use reverse-domain syntax.
- version ranges use the current Turboism version-range grammar.
A dependency record controls resolution and ordering. It does not make another plugin's private implementation package a public Java dependency.
Permissions
Each permission record contains a known ID, application or user scope, and a non-empty reason.
{
"id": "turboism.action.register",
"scope": "application",
"reason": "Registers the Hello action."
}Declare only permissions required by the SDK services the plugin calls. Permission does not guarantee Provider availability or bypass operation validation.
Resources and localization
Resource roots are normalized relative prefixes ending in /:
"resources": ["icons/"],
"i18n": {
"baseName": "META-INF/turboism/i18n/messages",
"locales": ["base", "en", "zh_Hans"]
}The JAR must then contain:
icons/hello.png
META-INF/turboism/i18n/messages.properties
META-INF/turboism/i18n/messages_en.properties
META-INF/turboism/i18n/messages_zh_Hans.propertiesDeclared roots and catalogs must exist. Ordinary non-class resources must belong to a declared root. Path traversal, absolute paths, backslashes, empty segments, and undeclared resources are rejected.
Build and validate
For an in-repository plugin module:
./gradlew :plugins:hello:test :plugins:hello:jar validatePluginMeta \
--no-daemon --console=plainThe repository uses Java 17 and writes module build results under:
build/worktree/<worktreeId>/<module>/libs/Strict JAR validation
The JAR itself is the installation file. Runtime treats a selected JAR as untrusted local input and validates:
- a regular file with no symbolic link in the source chain, capped at 16 MiB;
- exactly one
META-INF/turboism/plugin.json; - descriptor ID, version, and API-range consistency;
- no copied SDK, Runtime, test, test-framework, or Live2D classes;
- no native binaries or installer payloads;
- no nested JARs;
- declared entrypoints, resource roots, and i18n catalogs present;
- no undeclared ordinary resources.
Managed installation stages the validated JAR and applies it after Cubism restarts. Do not create a plugin by renaming an arbitrary ZIP or JAR. Always distribute the JAR produced by the Gradle build.