Lumina Plugin Ecosystem (Developer Preview)
April 27, 2026 · View on GitHub
建议先阅读:
docs/plugin-open-strategy.mddocs/plugin-manifest.v1.mddocs/appearance-plugin-guide.mdpackages/plugin-api/index.d.tspackages/plugin-ui/README.md
Lumina now exposes a first-party plugin runtime for developers.
Plugin locations
Lumina discovers plugins from these folders (in order):
- Workspace:
<vault>/.lumina/plugins - User:
<app_data>/plugins - Built-in: bundled app resources
If multiple plugins share the same id, the first one found wins (workspace overrides user overrides built-in).
Plugin manifest
Each plugin lives in its own folder and must include plugin.json:
{
"id": "hello-lumina",
"name": "Hello Lumina",
"version": "0.1.0",
"description": "Example plugin",
"author": "Lumina",
"entry": "index.js",
"min_app_version": "0.1.0",
"api_version": "1",
"permissions": [
"commands:*",
"events:*",
"vault:*",
"workspace:*",
"editor:*",
"ui:*",
"storage:*",
"network:*",
"runtime:*"
],
"enabled_by_default": true,
"is_desktop_only": false
}
Required fields
id: unique plugin identifiername: display nameversion: semantic version stringentry: JavaScript entry file path relative to plugin folder
Optional fields
description,author,homepagemin_app_version,api_version,is_desktop_onlypermissions: capability listenabled_by_default: defaults totrue
Compatibility behavior
- If
api_versiondoes not match host API version, plugin will not load. - If
min_app_versionis greater than current app version, plugin will not load. - Incompatible reasons are shown in the Installed Plugins modal (Ribbon → Puzzle icon).
Theme plugins
Theme plugins extend the manifest with a theme block:
{
"theme": {
"auto_apply": true,
"tokens": { "--accent": "#ff7a59" },
"light": { "--background": "#fafafa" },
"dark": { "--background": "#0e0f12" }
}
}
tokens is shared between modes; light / dark override per mode. With auto_apply: true the runtime applies the preset on load.
Entry contract
Lumina executes plugin entry as CommonJS-style code. The entry must export a setup function:
module.exports = function setup(api, plugin) {
// register features
return () => {
// optional cleanup when plugin unloads
};
};
You can also return { dispose() {} }.
Runtime API
api.meta
Plugin metadata:
id,name,version,source,permissions
api.logger
info(message)warn(message)error(message)
api.ui
notify(message)injectStyle(css, scopeId?)cssalso supports{ css, scopeId?, global?, layer? }layer:base | theme | component | override(injection order low -> high)
setThemeVariables(record)registerRibbonItem({ id, title, icon?, section?, order?, run })registerStatusBarItem({ id, text, align?, order?, run? })registerSettingSection({ id, title, html })registerContextMenuItem({ id, title, order?, run })registerCommandPaletteGroup({ id, title, commands })
api.theme
registerPreset({ id, name?, tokens?, light?, dark? })applyPreset(id)setToken({ token, value, mode? })resetToken({ token, mode? })
api.vault
getPath()readFile(path)writeFile(path, content)deleteFile(path)renameFile(oldPath, newPath)moveFile(sourcePath, targetFolder)listFiles()
api.metadata
getFileMetadata(path)returns:frontmatterlinkstags
api.commands
registerSlashCommand({ key, description, prompt })- Returns
unregister()cleanup function registerCommand({ id, title, description?, hotkey?, run })- Appears in command palette
- Supports default hotkey and conflict detection
api.workspace
getPath()getActiveFile()openFile(path)readFile(path)writeFile(path, content)registerPanel({ id, title, html })registerTabType({ type, title, render(payload) })openRegisteredTab(type, payload?)mountView({ viewType, title, html })registerShellSlot({ slotId, html, order? })registerLayoutPreset({ id, ...layout })applyLayoutPreset(id)
Workspace/vault operations are restricted to the current workspace path.
api.editor
getActiveFile()getActiveContent()setActiveContent(next)replaceRange(start, end, next)registerDecoration(className, css)getSelection()registerEditorExtension(cmExtension)for CodeMirror extensionsregisterEditorExtension({ id, css?, layer?, scopeId? })for style-only editor extensions
api.render
registerMarkdownPostProcessor({ id, process })registerCodeBlockRenderer({ id, language, render })registerReadingViewPostProcessor({ id, process(container) })(supports cleanup return)
api.storage
get(key)set(key, value)remove(key)
Data is namespaced by plugin id in local storage.
api.events
on("app:ready" | "workspace:changed" | "active-file:changed", handler)
api.network
fetch(input, init)
api.runtime
setInterval(handler, ms)clearInterval(id)setTimeout(handler, ms)clearTimeout(id)
api.interop
openExternal(url)
Permission model
Every sensitive API checks permissions declared in plugin.json.
commands:*(commands:register)events:*(events:subscribe)vault:*(vault:read,vault:write,vault:delete,vault:move,vault:list)metadata:readworkspace:*(workspace:read,workspace:open,workspace:panel,workspace:tab)editor:*(editor:read,editor:write,editor:decorate)ui:*(ui:notify,ui:theme,ui:decorate)storage:*(storage:read,storage:write)network:*(network:fetch)runtime:*(runtime:timer)interop:*(interop:open-external)
You can also use "*" to allow all capabilities. namespace:* wildcard is also supported.
Plugin manager (UI)
Click the Puzzle icon in the ribbon to open the Installed Plugins modal. Two sections live there:
Plugins (Developer Preview) — the top section:
- Refresh plugin discovery
- Reload plugin runtime
- Enable/disable plugins
- Open the workspace plugin folder
- Scaffold an example plugin
- Scaffold a theme plugin template
- Scaffold a UI-overhaul plugin template
- Toggle Appearance Safe Mode (disables appearance-heavy plugins)
- Unload all plugin styles with one click
Plugin Style Runtime (Dev) — the bottom section, inspect:
- active style layers
- selector conflicts across plugins
Quick start
- Open a workspace in Lumina.
- Click the Puzzle icon in the ribbon.
- Click Scaffold Example Plugin.
- Enable
hello-luminaif it isn't already. - In the AI chat input, type
/hello-luminaand send.
Frontend IPC handlers
The plugin runtime is exposed to the renderer through Electron IPC handlers in electron/main/handlers/plugins.ts. You only need these if you're embedding the plugin system, not for writing a plugin (use api.* for that).
plugin_list({ workspacePath? })→PluginInfo[]plugin_read_entry({ pluginId, workspacePath? })→{ info, code }plugin_get_workspace_dir({ workspacePath })→stringplugin_scaffold_example({ workspacePath })→string(created dir)plugin_scaffold_theme({ workspacePath })→stringplugin_scaffold_ui_overhaul({ workspacePath })→string