This skill should be used when the user asks to "create a koreader plugin", "add a menu item", "create a widget", "save settings", "koreader ui", "lua widget", "koreader api", "dispatcher action", or is working on KOReader e-reader plugin development. Provides comprehensive guidance for KOReader's Lua plugin architecture.
/plugin marketplace add uttam-salamander/koreader-dev/plugin install uttam-salamander-koreader-dev@uttam-salamander/koreader-devThis skill inherits all available tools. When active, it can use any tool Claude has access to.
examples/minimal-plugin/_meta.luaexamples/minimal-plugin/main.luareferences/patterns.mdreferences/widgets.mdGuidance for developing plugins for KOReader, the open-source e-book reader application. KOReader plugins are written in Lua and extend the reader with custom functionality.
Every KOReader plugin requires two files in a .koplugin directory:
pluginname.koplugin/
├── _meta.lua # Plugin metadata (required)
└── main.lua # Plugin entry point (required)
Define plugin metadata for KOReader's plugin loader:
local _ = require("gettext")
return {
name = "pluginname", -- Internal identifier (lowercase, no spaces)
fullname = _("Plugin Name"), -- Display name (translatable)
description = _([[Brief description of what the plugin does.]]),
}
Plugin entry point extending WidgetContainer:
local WidgetContainer = require("ui/widget/container/widgetcontainer")
local _ = require("gettext")
local MyPlugin = WidgetContainer:extend{
name = "pluginname",
is_doc_only = false, -- true if plugin only works when document is open
}
function MyPlugin:init()
self.ui.menu:registerToMainMenu(self)
end
function MyPlugin:addToMainMenu(menu_items)
menu_items.pluginname = {
text = _("Plugin Name"),
sorting_hint = "tools",
sub_item_table = {
-- Menu items here
},
}
end
return MyPlugin
Common imports for KOReader plugins:
-- UI and widgets
local UIManager = require("ui/uimanager")
local WidgetContainer = require("ui/widget/container/widgetcontainer")
local InfoMessage = require("ui/widget/infomessage")
local InputDialog = require("ui/widget/inputdialog")
local ButtonDialog = require("ui/widget/buttondialog")
local Menu = require("ui/widget/menu")
-- Data and settings
local DataStorage = require("datastorage")
local LuaSettings = require("luasettings")
-- Utilities
local Dispatcher = require("dispatcher")
local _ = require("gettext")
local T = require("ffi/util").template
Use LuaSettings to save and load plugin data:
function MyPlugin:loadSettings()
self.settings = LuaSettings:open(
DataStorage:getSettingsDir() .. "/pluginname.lua"
)
self.data = self.settings:readSetting("data_key") or {}
end
function MyPlugin:saveSettings()
self.settings:saveSetting("data_key", self.data)
self.settings:flush()
end
Call loadSettings() in init() and saveSettings() when data changes.
local InfoMessage = require("ui/widget/infomessage")
UIManager:show(InfoMessage:new{
text = _("Message to display"),
timeout = 3, -- Auto-dismiss after 3 seconds (optional)
})
local InputDialog = require("ui/widget/inputdialog")
local input_dialog
input_dialog = InputDialog:new{
title = _("Enter Value"),
input = "default text",
input_hint = _("Placeholder text"),
buttons = {{
{
text = _("Cancel"),
callback = function()
UIManager:close(input_dialog)
end,
},
{
text = _("OK"),
callback = function()
local value = input_dialog:getInputText()
UIManager:close(input_dialog)
-- Process value
end,
},
}},
}
UIManager:show(input_dialog)
input_dialog:onShowKeyboard()
local ButtonDialog = require("ui/widget/buttondialog")
local button_dialog
button_dialog = ButtonDialog:new{
title = _("Choose Option"),
buttons = {
{{
text = _("Option 1"),
callback = function()
UIManager:close(button_dialog)
-- Handle option 1
end,
}},
{{
text = _("Option 2"),
callback = function()
UIManager:close(button_dialog)
-- Handle option 2
end,
}},
},
}
UIManager:show(button_dialog)
local Menu = require("ui/widget/menu")
local menu
menu = Menu:new{
title = _("Select Item"),
item_table = {
{ text = "Item 1", callback = function() end },
{ text = "Item 2", callback = function() end },
},
close_callback = function()
UIManager:close(menu)
end,
}
UIManager:show(menu)
function MyPlugin:addToMainMenu(menu_items)
menu_items.pluginname = {
text = _("Plugin Name"),
sorting_hint = "tools", -- or "search", "main", "setting"
sub_item_table = {
{
text = _("Action"),
callback = function()
self:doAction()
end,
},
{
text = _("Toggle Feature"),
checked_func = function()
return self.settings:isTrue("feature_enabled")
end,
callback = function()
self.settings:toggle("feature_enabled")
end,
},
{
text = _("Submenu"),
sub_item_table = {
-- Nested items
},
},
},
}
end
"main" - Top-level menu"search" - Search section"tools" - Tools section"setting" - Settings sectionRegister actions for gestures and keyboard shortcuts:
function MyPlugin:onDispatcherRegisterActions()
Dispatcher:registerAction("pluginname_action", {
category = "none",
event = "PluginAction",
title = _("Plugin Action"),
general = true, -- Available globally (not just in reader)
})
end
function MyPlugin:onPluginAction()
-- Handle the action
return true
end
Call self:onDispatcherRegisterActions() in init().
When is_doc_only = true, access document via self.ui.document:
-- Get current page
local page = self.ui.document:getCurrentPage()
-- Get total pages
local total = self.ui.document:getPageCount()
-- Get document info
local props = self.ui.document:getProps()
local title = props.title
local author = props.authors
function MyPlugin:init()
-- Called when plugin loads
self:loadSettings()
self:onDispatcherRegisterActions()
self.ui.menu:registerToMainMenu(self)
end
function MyPlugin:onFlushSettings()
-- Called when KOReader saves settings
self:saveSettings()
end
function MyPlugin:onCloseDocument()
-- Called when document closes (if is_doc_only)
self:saveSettings()
end
KOReader uses an event system for communication:
-- Send event
UIManager:broadcastEvent(Event:new("CustomEvent", data))
-- Handle event
function MyPlugin:onCustomEvent(data)
-- Process event
return true -- Event handled
end
For detailed patterns and advanced techniques:
references/widgets.md - Complete widget referencereferences/patterns.md - Common plugin patternsWorking examples in examples/:
examples/minimal-plugin/ - Minimal plugin templateThis skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.