The SDK
dev.nuclr:platform-sdk is published to Maven Central and is the whole
contract between a plugin and the application: a handful of interfaces, a plugin
context, and the model types a panel or a viewer works with.
<dependency>
<groupId>dev.nuclr</groupId>
<artifactId>platform-sdk</artifactId>
<version>5.0.0</version>
<scope>provided</scope>
</dependency>
The scope matters: the host supplies the SDK at runtime, and a copy bundled inside
your plugin would be loaded by a different classloader than the one the application
hands you objects from. Keep it provided.
The SDK uses Lombok internally. Since JDK 23 javac no longer runs
annotation processors that merely sit on the classpath, so a plugin that uses Lombok
has to opt back in - either <proc>full</proc> on the
compiler plugin, or Lombok declared in
<annotationProcessorPaths>.
SDK 5.0.0 changes
SDK 5.0.0 adds a host-owned credential store for secrets such as API tokens,
passwords and access keys. It is deliberately separate from the ordinary settings
store: credentials are persisted by the operating system, scoped to the plugin's
stable ID, and are never copied into a plaintext settings file.
| New API | Purpose |
NuclrPluginContext.getCredentialStore() |
Gets the host-owned store for the current plugin. Obtaining it does not unlock or access the OS keyring. |
NuclrCredentialStore.get(key) |
Returns an Optional<String>; empty means the key is absent. |
NuclrCredentialStore.set(key, secret) |
Creates or replaces a nonempty secret. Whitespace in the secret is preserved. |
NuclrCredentialStore.delete(key) |
Removes a secret; deleting a missing key is a successful no-op. |
NuclrCredentialException |
Reports UNAVAILABLE storage separately from an ACCESS_FAILED operation. |
import java.util.Optional;
import dev.nuclr.platform.NuclrCredentialException;
import dev.nuclr.platform.NuclrCredentialStore;
NuclrCredentialStore credentials = context.getCredentialStore();
try {
Optional<String> token = credentials.get("access-token");
credentials.set("access-token", userEnteredToken);
} catch (NuclrCredentialException e) {
// Show a safe recovery message; never log token or secret values.
}
Credential operations may block or prompt to unlock the OS store, so call
get, set and delete off the Swing event-dispatch
thread. Keys must be nonblank and contain neither NUL nor |. Keep returned
secrets only as long as needed: do not log them, cache them, or use settings as a
plaintext fallback. Use the settings store for non-secret preferences.
Migration from 4.0.0
- Change the Maven dependency to
5.0.0 and set platformSdkVersion to 5.0.0 in plugin.json.
- Move passwords, tokens and keys out of
NuclrSettings and into context.getCredentialStore().
- Handle
NuclrCredentialException without exposing the secret, and provide a useful recovery path when the OS store is unavailable.
- No changes are required to the three plugin interfaces or their lifecycle unless you want to use credential storage.
The three plugin types
BaseNuclrPlugin is a sealed interface with exactly three permitted
subtypes. Implement one of them - never the base directly - and declare the matching
type in the manifest. Everything a plugin says about itself
lives in that manifest; the interface only describes what it does.
QuickViewNuclrPlugin
Renders a preview in the quick-view pane beside the listing.
panel() returns the Swing component to show.
supports(NuclrResource) decides whether this file is yours - and may be called off the event thread, so keep it thread-safe and free of modal UI.
openResource(resource, cancelled) loads or refreshes it, doing the heavy work in the background and watching the cancel flag.
- When several viewers accept the same file, the manifest's
priority breaks the tie - lower wins.
FilePanelNuclrPlugin
Provides a browser pane: local drives, archives, containers, buckets, remote hosts.
openResource(...) returns the entries and the column names for a location; a streaming overload paints entries as they are discovered.
getPluginMenuItems() supplies the commands on the function-key bar, which is how a panel decides what F5 or Alt F7 mean while it has the focus.
getCurrentLocationDisplayText() and getSelectionSummaryText(...) feed the path and status bars.
openTerminal(...) optionally hands the embedded console a shell rooted at the current location.
FullscreenNuclrPlugin
Takes over the whole window - an editor or a full-window viewer.
panel() returns the UI and openResource(...) opens the file.
- Whether it is read-only or not is the manifest's
role: Viewer or Editor.
All three share the lifecycle on BaseNuclrPlugin -
preinit(context), init(),
updateTheme(...), unload() - plus focus callbacks,
uuid() for instance identity, and act(...) for the actions
the host and other plugins send. The context gives you the event bus, the current
theme, a namespaced settings store and the locale.
Threading is the one rule worth repeating: supports(...) can run on a
background thread because deciding whether you can open something may touch the
filesystem or the network, and openResource(...) must do its work
asynchronously, poll the supplied cancel flag, and push every UI change back onto
the event-dispatch thread.
The plugin.json manifest
Every bundle carries a plugin.json at its root, and one bundle may ship
several plugins. It is the only source of a plugin's identity.
{
"schemaVersion": 1,
"version": "1.0.0",
"platformSdkVersion": "5.0.0",
"plugins": [
{
"class": "com.example.MyQuickViewPlugin",
"type": "QuickView",
"id": "com.example.myplugin.quickview",
"name": "My Quick Viewer",
"version": "1.0.0",
"description": "Example quick view plugin.",
"author": "Your Name",
"license": "Apache-2.0",
"developer": "Community",
"singleton": true,
"priority": 100
}
]
}
| Field | Means |
class | The fully-qualified class implementing one of the three interfaces |
type | QuickView, FilePanel or Fullscreen - must match the interface the class implements |
id | A stable unique identifier, reverse-domain style |
developer | Official or Community |
singleton | Whether at most one instance may exist; a multi-instance plugin must return a distinct uuid() per instance |
priority | Quick view only: lower wins when several viewers accept the same file |
role | Fullscreen only: Viewer or Editor |
name, description, author, license | What the plugin manager shows |
website, pageUrl, docUrl | Optional links |
Let Maven fill the version in - declare "version": "${project.version}"
and filter only plugin.json. Bundles routinely carry icons and
native libraries that must be copied byte-for-byte.
Packaging and signing
A plugin ships as a ZIP whose root holds:
plugin.json the manifest above
my-plugin.jar your classes
lib/ runtime dependencies - never the SDK, which is provided
... any assets you need
Commander verifies a bundle's detached signature before it loads anything from it.
Plugins installed from this site are signed; a bundle you build yourself is not, so
it loads through the route below.
Running your own build
A bundle you built carries no signature the application can check, so three things
must hold before it will load:
-
Turn on Developer mode in Settings (Ctrl P,
under Advanced). Turning it off again unloads every local plugin immediately and
keeps them from loading at the next start.
-
Accept the risk warning once - and again if its wording changes.
-
Approve the bundle itself - open the plugin manager with
F11 and use Install from file…. The approval is recorded
against the SHA-256 of the exact bytes you saw, so a ZIP that merely appears in
the folder is never loaded.
After that it behaves like any other plugin: it can be enabled and disabled, it is
still subject to the published blacklist, and the plugin manager marks it unverified
throughout. What it may not do is take an id that belongs to a signed plugin.
Building it with an AI agent
What this page teaches a person is also packaged for a coding agent, as an
Agent Skill - a folder the
agent reads before it writes anything, so that asking for a quick
viewer for some format produces a complete, packaged, validated bundle rather
than a plausible-looking sketch. It is the open skill format rather than any
one vendor's, and it is Apache-2.0, like the SDK.
git clone https://github.com/nuclrdev/skills.git
Copy or symlink skills/nuclr-commander-plugins/ into whichever
directory your agent reads skills from; an agent that installs straight from
Git can be given the repository URL and that subdirectory instead. From then
on it selects the skill by itself when you ask for a plugin, and you can name
it explicitly as nuclr-commander-plugins.
Inside the folder:
SKILL.md - how to establish the contract, choose one of the three types, and what counts as finished.
- A self-contained snapshot of the SDK contract, plus one reference per plugin type: the lifecycle and threading rules, the manifest, and the Maven bundle layout.
scripts/validate_plugin.py - a standalone checker, no Maven and no Commander install required.
python3 scripts/validate_plugin.py my-plugin --archive target/my-plugin-1.0.0.zip
The validator is worth running whether or not an agent wrote the code: it
checks the SDK dependency and its scope, the manifest's schema, required
fields and vocabulary, priority and role per type,
id and class uniqueness, the flat ZIP layout, that no
${...} placeholder survived into the packaged manifest, that the
SDK is not bundled, and that every class the manifest declares is really in
the JAR. It needs Python 3.10 or newer, and exits non-zero on any error.
An agent writing the bundle changes nothing about how it loads: it is still
unsigned, so it takes the three steps above, and it is still your plugin. Run
the tests, install the ZIP, and confirm in the plugin manager that the build
you just made is the one that loaded.
Examples on GitHub
Every panel and viewer a fresh install ships with is a plugin built against this
same SDK, each published from its own repository.
File panels browser panes
Quick viewers preview panes
Fullscreen whole-window tools
These are the official plugins; the plugin catalogue lists
every published one, each with its README, its versions and a link to its source.