
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
@pstdio/tiny-plugins
Advanced tools
Plugin host for OPFS plugins with manifest validation, command routing, and settings storage.
OPFS-backed plugin host with manifest validation, hot-reload watchers, and command routing. Load sandboxed plugins from the browser's file system, surface their commands, and keep per-plugin settings in sync.
manifest.json and entry module.fs, log, and settings) that plugins can rely on.@pstdio/tiny-ui so UI shells can bridge into plugin code with a single call.npm install @pstdio/tiny-plugins
import { createHost } from "@pstdio/tiny-plugins";
const host = createHost({
root: "plugins", // OPFS directory containing plugin folders
dataRoot: "plugin_data", // optional, defaults to "plugin_data"
notify: (level, message) => console[level]("[plugins]", message),
});
await host.start();
host.onPluginChange((pluginId, payload) => {
console.log(`reloaded ${pluginId}`, payload.manifest);
});
await host.runCommand("sample-plugin", "sayHello", { name: "Tiny" });
await host.updateSettings("sample-plugin", { enabled: true });
await host.stop();
Call start() once to read every plugin directory, validate manifests, import the entry module, and activate each plugin. stop() disposes watchers, revokes module URLs, and invokes deactivate() if it exists.
start() enumerates directories under root, validates manifest.json via core/manifest.ts, imports the declared entry, and calls plugin.activate(ctx).watch: true (default) the host wires watchPluginsRoot and watchPluginDir so that file changes trigger a reload, emit pluginChange, and refresh merged dependencies.runCommand(pluginId, commandId, params) dispatches through the internal registry.dataRoot). The host exposes settings.read/settings.write in the API and mirrors updates through onSettingsChange.createHost(options) returns an object with the following capabilities:
start(), stop().onPluginChange, onDependencyChange, onSettingsChange, onStatus, onError.getMetadata(), getPluginDependencies(), listCommands().runCommand(pluginId, commandId, params?), updateSettings(pluginId, value), readSettings(pluginId).createHostApiFor(pluginId) returns the same host API handed to plugins during activate(). @pstdio/tiny-ui calls this helper inside its iframe bridge so UI surfaces share the exact capabilities via api.call(method, params?).Each plugin-facing API is exposed through ctx.api.call(method, params?) and includes:
fs.* methods scoped to the plugin's directory (readFile, writeFile, deleteFile, moveFile, exists, mkdirp).log.* helpers (statusUpdate, info, warn, error) that forward to the host's notify callback and emit runtime events.settings.read / settings.write for persisting JSON-serializable state in the plugin's data directory.The runtime layer builds on createHost to provide higher-level orchestration and React integrations.
createPluginHostRuntime(options)manifest.surfaces.Tool instances through createToolsForCommands.import { createPluginHostRuntime } from "@pstdio/tiny-plugins";
const runtime = createPluginHostRuntime({ root: "plugins" });
const tools = runtime.getPluginTools(); // mirrors host commands
runtime.subscribeToPluginCommands((commands) => {
console.table(commands);
});
usePluginHost and usePluginsusePluginHost(runtime) wraps the runtime API for React apps, returning commands, tools, settings, loading state, and helpers that proxy through to the host (e.g., runCommand, readSettings).usePlugins(host) is a lightweight hook for consumers that already instantiated a host themselves.const runtime = useMemo(() => createPluginHostRuntime({ root: "plugins" }), []);
const { commands, tools, runCommand } = usePluginHost(runtime);
return <PluginList commands={commands} onRun={(command) => runCommand(command.pluginId, command.id)} />;
Both hooks call back into the core host, so updates from the filesystem or settings propagate automatically.
Use subscribeToPluginFiles when you need batched file-change notifications without adopting the full runtime:
import { createHost, subscribeToPluginFiles } from "@pstdio/tiny-plugins";
const host = createHost({ root: "plugins" });
await host.start();
const unsubscribe = subscribeToPluginFiles(host, (events) => {
events.forEach(({ pluginId, payload }) => {
console.log(pluginId, payload.paths);
});
});
Each batch corresponds to a single microtask, preserving change order while coalescing rapid file updates.
Browser helpers in helpers/plugin-downloads.ts package OPFS directories as ZIP archives:
downloadPluginSource({ pluginId, pluginsRoot, label? })downloadPluginData({ pluginId, pluginDataRoot, label? })downloadPluginBundle({ pluginId, pluginsRoot, pluginDataRoot, label? })downloadDirectory, createZipBlob, createZipFromDirectoriesimport { downloadPluginBundle } from "@pstdio/tiny-plugins";
await downloadPluginBundle({
pluginId: "sample-plugin",
pluginsRoot: "plugins",
pluginDataRoot: "plugin_data",
label: "sample",
});
These utilities reuse @pstdio/opfs-utils to walk directories and fflate to stream ZIP contents, making it easy to export plugin code, data, or both for sharing/debugging.
manifest.json must satisfy the rules enforced in core/manifest.ts:
id, name, version (valid semver), api (matches host API version, e.g. "v1"), and entry (module path).description, dependencies (merged into host-wide map), commands (array of { id, title, ... }), and ui (forwarded verbatim).settingsSchema is accepted and surfaced by the runtime so UIs can render forms or validation.surfaces (record of surface metadata) is consumed by the runtime to drive Tiny UI panes or other host experiences.At load time the host ensures manifest.id matches the directory name, the api matches the host's hostApiVersion, and warns when entry is not a JavaScript/TypeScript module.
@pstdio/tiny-ui composes Tiny Plugins to render plugin user interfaces inside sandboxed iframes. It uses createHostApiFor(pluginId) to hand the exact same host bridge into each iframe, ensuring parity between headless command execution and interactive surfaces.
When a manifest declares surfaces, the runtime exposes them via getPluginSurfaces() and subscribeToPluginSurfaces(), letting Tiny UI mount views like settings panels or dashboards. Use the download helpers above to package those surfaces alongside their plugin code for distribution.
Need help? File an issue or join the conversation in the Kaset repository.
FAQs
Plugin host for OPFS plugins with manifest validation, command routing, and settings storage.
We found that @pstdio/tiny-plugins demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.