The exception first-class provider: a cross-process bridge over line-delimited JSON-RPC.
Hosihikari.PluginProvider.Rpc drives a child process with line-delimited JSON-RPC 2.0 over stdio. The bridge reads only the command from the manifest and the protocol. Unload means the process exits, taking the child process and its state with it.
Pick the shim variant for the server (see Quick start); the examples below use LeviLamina:
lip install github.com/HosihikariFramework/Shim#levilaminalip install github.com/HosihikariFramework/RpcProviderThe RPC templates cover Node.js/TypeScript, Python, and Kotlin/JVM.
The plugin package also declares a dependency on hosihikari-rpc. The operator provides the executable: put it on PATH or use an absolute path.
Package layout (Node)
Section titled “Package layout (Node)”plugins/hello-node/ manifest.json main.mjs hosihikari.mjs ← copied from the provider package's bindings/node hello-node.conf{ "name": "hello-node", "entry": "main.mjs", "type": "hosihikari", "version": "1.0.0", "dependencies": [ { "name": "hosihikari" }, { "name": "hosihikari-rpc" } ], "extraInfo": { "api": "2", "format_version": "1", "provider": "rpc", "command": "[\"node\", \"./hosihikari.mjs\", \"./main.mjs\"]" }}command is a JSON array encoded as a string; the working directory is the package directory, and no shell is involved.
Copy the binding from the installed provider package:
Copy-Item <server>/plugins/hosihikari-rpc/bindings/node/hosihikari.mjs .Plugin
Section titled “Plugin”import fs from 'node:fs';import path from 'node:path';
export default { onLoad(ctx) { fs.writeFileSync(path.join(ctx.dataDirectory, 'loaded.txt'), 'loaded'); ctx.logInfo(`loaded, package ${ctx.packageDirectory}`); },
onEnable(ctx) { const greeting = ctx.config('greeting'); fs.writeFileSync(path.join(ctx.dataDirectory, 'enabled.txt'), greeting ?? ''); ctx.logInfo(`enabled, greeting=${greeting}`); },
onDisable(ctx) { return true; },
onUnload(ctx) {},};Hooks may be async. Return values: undefined or true allows; false or { allowed: false, reason } refuses; a thrown exception is a fault.
Context
Section titled “Context”ctx provides:
| Member | Meaning |
|---|---|
name / packageDirectory / dataDirectory |
Plugin name, package directory, data directory. |
config(key) / configObject |
The layered configuration. |
logInfo / logWarn / logError |
Structured logs over the protocol. |
writeData(file, text) |
Write a file under the data directory. |
publish(topic, json) |
Publish an event. |
subscribe(topic, fn, { sync }) |
Subscribe; with sync: true the publisher waits for an ack. |
runLater(seconds, fn) / runEvery(seconds, fn) |
Timers. |
Events are asynchronous by default. Disabling closes the gate; subscription callbacks and timers stop, and a refused disable reopens it. Unload ends the child process.
stdout carries the protocol; stderr carries text logs. The Node binding redirects console.log to stderr; do not write to stdout.
Bindings
Section titled “Bindings”The provider package ships reference bindings:
bindings/node/hosihikari.mjsbindings/quickjs/hosihikari.js(requiresqjs)bindings/python/hosihikari.py(module-levelon_loadand friends)bindings/java/hosihikari(shared by Java and Kotlin;commandpoints athosihikari.Binding)
The command forms and context APIs are in the RpcProvider README.
Endstone and PATH
Section titled “Endstone and PATH”The Endstone CLI replaces the child process PATH with Python’s sys.path; if the executable cannot be found, use an absolute path in command.
Protocol
Section titled “Protocol”A custom binding implements the same protocol:
- Host → plugin:
hosihikari/hello,hosihikari/enable,hosihikari/disable,hosihikari/unload,hosihikari/events/deliver - Plugin → host:
hosihikari/events/publish,hosihikari/events/subscribe,hosihikari/events/unsubscribe,hosihikari/log - Unload:
unloadrequest → close stdin → wait 5 seconds → kill the process tree.
