@kurokeita/add-skill 1.20.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -8
- package/dist/bin/cli.js +576 -792
- package/dist/skills/git-commit/SKILL.md +101 -37
- package/dist/skills/handoff/SKILL.md +16 -0
- package/dist/skills/init-agents-md/SKILL.md +51 -0
- package/dist/skills/init-agents-md/references/agents_template.md +42 -0
- package/dist/skills/init-agents-md/references/patterns_template.md +19 -0
- package/dist/skills/koreader-plugin-development/SKILL.md +141 -0
- package/dist/skills/koreader-plugin-development/examples/2-example-patch.lua +17 -0
- package/dist/skills/koreader-plugin-development/examples/_meta.lua +6 -0
- package/dist/skills/koreader-plugin-development/examples/helloworld.lua +36 -0
- package/dist/skills/koreader-plugin-development/references/debugging.md +92 -0
- package/dist/skills/koreader-plugin-development/references/events-and-widgets.md +102 -0
- package/dist/skills/koreader-plugin-development/references/patches.md +118 -0
- package/dist/skills/koreader-plugin-development/references/plugin-anatomy.md +97 -0
- package/dist/skills/statusline-setup/SKILL.md +211 -66
- package/dist/skills/universalize-agents/SKILL.md +245 -0
- package/dist/skills/universalize-agents/reference/hook-templates/README.md +30 -0
- package/dist/skills/universalize-agents/reference/hook-templates/claude-code.json +15 -0
- package/dist/skills/universalize-agents/reference/hook-templates/codex.toml +10 -0
- package/dist/skills/universalize-agents/reference/hook-templates/copilot.json +9 -0
- package/dist/skills/universalize-agents/reference/hook-templates/gemini.json +11 -0
- package/dist/skills/universalize-agents/reference/mapping.md +100 -0
- package/dist/skills/universalize-agents/scripts/agent-setup.ps1 +83 -0
- package/dist/skills/universalize-agents/scripts/agent-setup.sh +98 -0
- package/dist/skills/update-agents-md/SKILL.md +43 -0
- package/package.json +2 -5
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Events and Widgets
|
|
2
|
+
|
|
3
|
+
## Class hierarchy
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
EventListener -- ui.widget.eventlistener (handleEvent)
|
|
7
|
+
└── Widget -- ui.widget.widget
|
|
8
|
+
└── WidgetContainer -- ui.widget.container.widgetcontainer
|
|
9
|
+
├── FrameContainer, CenterContainer, BottomContainer, ...
|
|
10
|
+
├── InputContainer (gesture handling)
|
|
11
|
+
├── ReaderUI / FileManager (top-level hosts)
|
|
12
|
+
└── any plugin extending WidgetContainer
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
All widgets inherit `handleEvent`. `WidgetContainer` overrides it to first propagate to children; if no child returns `true`, the container's own `on<EventName>` runs.
|
|
16
|
+
|
|
17
|
+
## Sending an event
|
|
18
|
+
|
|
19
|
+
```lua
|
|
20
|
+
widget:handleEvent(Event:new("Timeout")) -- direct, may be unsafe if widget destroys itself
|
|
21
|
+
UIManager:sendEvent(Event:new("PageUpdate", 5)) -- from topmost widget down
|
|
22
|
+
UIManager:broadcastEvent(Event:new("CloseDocument")) -- to every widget on the stack
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`Event:new(name, arg1, arg2, ...)` packs args; the receiver implements `on<Name>(self, arg1, arg2, ...)`.
|
|
26
|
+
|
|
27
|
+
## The event consumption contract
|
|
28
|
+
|
|
29
|
+
```lua
|
|
30
|
+
function MyPlugin:onPageUpdate(new_pageno)
|
|
31
|
+
if not self.enabled then return end -- do not consume
|
|
32
|
+
self:doWork(new_pageno)
|
|
33
|
+
return true -- stop propagation
|
|
34
|
+
end
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
- `return true` — consume; sibling widgets and the container's own handler are skipped.
|
|
38
|
+
- `return nil`/`false` — let propagation continue.
|
|
39
|
+
|
|
40
|
+
Consumption is the most common source of "my plugin runs but the reader stops responding to taps" bugs. Consume only events whose semantics you fully replace.
|
|
41
|
+
|
|
42
|
+
## Common reader events
|
|
43
|
+
|
|
44
|
+
| Event | When | Args |
|
|
45
|
+
|-------|------|------|
|
|
46
|
+
| `ReaderReady` | After document opens and modules initialized | `doc_settings` |
|
|
47
|
+
| `CloseDocument` | Before tearing down ReaderUI | — |
|
|
48
|
+
| `PosUpdate` | Reader rolling module signals scroll position change | `pos` |
|
|
49
|
+
| `UpdatePos` | Typesetting changed; recompute layout | — |
|
|
50
|
+
| `PageUpdate` | Page number changed | `new_pageno` |
|
|
51
|
+
| `PageChangeAnimation` | Animated page turn | `forward` |
|
|
52
|
+
| `BookMetadataChanged` | Metadata edited | `prop_updated` |
|
|
53
|
+
| `Suspend` / `Resume` | Device sleep cycle | — |
|
|
54
|
+
| `NetworkConnected` / `NetworkDisconnected` | Network state | — |
|
|
55
|
+
|
|
56
|
+
## Common file-manager events
|
|
57
|
+
|
|
58
|
+
| Event | When |
|
|
59
|
+
|-------|------|
|
|
60
|
+
| `PathChanged` | Directory navigated |
|
|
61
|
+
| `BookmarkAdded` / `BookmarkRemoved` | Bookmark mutated |
|
|
62
|
+
| `SetupShowReader` | About to launch Reader for a file |
|
|
63
|
+
|
|
64
|
+
For exhaustive coverage, search KOReader source for `Event:new("` — every emit is grep-able.
|
|
65
|
+
|
|
66
|
+
## UIManager lifecycle for widgets
|
|
67
|
+
|
|
68
|
+
```lua
|
|
69
|
+
UIManager:show(widget) -- push onto _window_stack, schedule paint
|
|
70
|
+
UIManager:setDirty(widget, "ui") -- request redraw
|
|
71
|
+
UIManager:close(widget) -- pop, schedule repaint of stack below
|
|
72
|
+
UIManager:scheduleIn(0.5, fn) -- run after 500ms
|
|
73
|
+
UIManager:unschedule(fn)
|
|
74
|
+
UIManager:nextTick(fn) -- defer to next event loop tick
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Always pair `show` with `close`. Widgets registered with `is_always_active = true` keep receiving events even when not at the top of the stack.
|
|
78
|
+
|
|
79
|
+
## Useful built-in widgets
|
|
80
|
+
|
|
81
|
+
| Widget | Purpose |
|
|
82
|
+
|--------|---------|
|
|
83
|
+
| `InfoMessage` | Modal info text with auto-close |
|
|
84
|
+
| `ConfirmBox` | Yes/no prompt with callbacks |
|
|
85
|
+
| `ButtonDialog` | Multi-button dialog from a 2D grid |
|
|
86
|
+
| `InputDialog` | Text input with buttons |
|
|
87
|
+
| `Notification` | Transient toast (no user dismissal) |
|
|
88
|
+
| `Menu` | Scrollable list of items |
|
|
89
|
+
| `KeyValuePage` | Two-column property display |
|
|
90
|
+
| `TrapWidget` | Block input while a long task runs |
|
|
91
|
+
|
|
92
|
+
All are under `ui.widget.*` and accept a table of properties to `:new{...}`.
|
|
93
|
+
|
|
94
|
+
## Draw-page code path (for performance work)
|
|
95
|
+
|
|
96
|
+
1. `ReaderView:recalculate` flags itself dirty.
|
|
97
|
+
2. UIManager main loop calls `ReaderView:paintTo`.
|
|
98
|
+
3. `ReaderView:paintTo` calls `document:drawPage`.
|
|
99
|
+
4. `document:drawPage` checks the cache; if hit, returns the cached buffer.
|
|
100
|
+
5. On miss, `document:renderPage` calls `_document:openPage`, `page:draw`, then caches the buffer.
|
|
101
|
+
|
|
102
|
+
Hooking `ReaderView:paintTo` from a plugin can layer custom overlays per page; do it sparingly and never block.
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# Patches (`koreader/patches/`)
|
|
2
|
+
|
|
3
|
+
User patches are arbitrary Lua files in `koreader/patches/` that the `userpatch` module applies during startup. Use them when:
|
|
4
|
+
|
|
5
|
+
- A plugin would be overkill for a one-line behavior change.
|
|
6
|
+
- The change must run before plugins or before the UI builds.
|
|
7
|
+
- Distributing a hotfix to users who can drop a single file into a directory.
|
|
8
|
+
|
|
9
|
+
## Priority ordering
|
|
10
|
+
|
|
11
|
+
`userpatch.applyPatches(priority)` is called multiple times during boot. Each call processes files whose name starts with the matching numeric prefix, sorted lexicographically.
|
|
12
|
+
|
|
13
|
+
| Filename prefix | Phase | Run point | Typical use |
|
|
14
|
+
|-----------------|-------|-----------|-------------|
|
|
15
|
+
| `1-` | early-once | Very early, before most modules load | Set globals, env vars, monkey-patch core utilities |
|
|
16
|
+
| `2-` | early | After core modules load, before UI | Override module methods that the UI then uses |
|
|
17
|
+
| `3-` | late | After UI is built | Replace running widgets, mutate menus |
|
|
18
|
+
|
|
19
|
+
A patch named `2-fix-readerfooter.lua` runs in the early phase; `3-add-menu.lua` runs late.
|
|
20
|
+
|
|
21
|
+
## Patch skeleton
|
|
22
|
+
|
|
23
|
+
```lua
|
|
24
|
+
-- 2-stay-on-cover.lua
|
|
25
|
+
-- Targets KOReader v2025.05+; remove if behavior is fixed upstream.
|
|
26
|
+
|
|
27
|
+
local ReaderRolling = require("apps.reader.modules.readerrolling")
|
|
28
|
+
local original_onReadSettings = ReaderRolling.onReadSettings
|
|
29
|
+
|
|
30
|
+
function ReaderRolling:onReadSettings(config)
|
|
31
|
+
local res = original_onReadSettings(self, config)
|
|
32
|
+
if config:nilOrTrue("stay_on_cover") then
|
|
33
|
+
self:gotoPos(0)
|
|
34
|
+
end
|
|
35
|
+
return res
|
|
36
|
+
end
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Always:
|
|
40
|
+
|
|
41
|
+
1. Capture the original function before replacement.
|
|
42
|
+
2. Call the original from inside your override unless the goal is total replacement.
|
|
43
|
+
3. Pass `self` and all args through unchanged.
|
|
44
|
+
|
|
45
|
+
## Patch idioms
|
|
46
|
+
|
|
47
|
+
### Add a method
|
|
48
|
+
|
|
49
|
+
```lua
|
|
50
|
+
local M = require("apps.reader.modules.readerbookmark")
|
|
51
|
+
function M:exportAll()
|
|
52
|
+
-- ...
|
|
53
|
+
end
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Wrap an existing method
|
|
57
|
+
|
|
58
|
+
```lua
|
|
59
|
+
local M = require("ui.widget.infomessage")
|
|
60
|
+
local orig = M.init
|
|
61
|
+
function M:init()
|
|
62
|
+
orig(self)
|
|
63
|
+
self.text = "[patched] " .. (self.text or "")
|
|
64
|
+
end
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Replace a method
|
|
68
|
+
|
|
69
|
+
```lua
|
|
70
|
+
local M = require("apps.reader.modules.readerfooter")
|
|
71
|
+
function M:onTapFooter() return false end
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Add a menu entry from a patch
|
|
75
|
+
|
|
76
|
+
A patch can mutate the menu builder used by the UI — the safer option is to attach to an existing module's `addToMainMenu`:
|
|
77
|
+
|
|
78
|
+
```lua
|
|
79
|
+
local M = require("apps.reader.modules.readermenu")
|
|
80
|
+
local orig = M.setUpdateItemTable
|
|
81
|
+
function M:setUpdateItemTable()
|
|
82
|
+
orig(self)
|
|
83
|
+
self.menu_items.tools.sub_item_table[#self.menu_items.tools.sub_item_table + 1] = {
|
|
84
|
+
text = "Patched entry",
|
|
85
|
+
callback = function() end,
|
|
86
|
+
}
|
|
87
|
+
end
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
For complex menu work, prefer a plugin.
|
|
91
|
+
|
|
92
|
+
## Version guarding
|
|
93
|
+
|
|
94
|
+
KOReader internals shift between releases. A patch that worked on v2025.04 may crash on v2025.07.
|
|
95
|
+
|
|
96
|
+
```lua
|
|
97
|
+
local Version = require("version")
|
|
98
|
+
local current = Version:getCurrentRevision()
|
|
99
|
+
if current < "v2025.05" then return end -- skip if older
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Always include a target-version comment header so users (and you, six months later) can audit at a glance.
|
|
103
|
+
|
|
104
|
+
## Removal
|
|
105
|
+
|
|
106
|
+
To disable a patch, delete it or rename it so the prefix no longer matches a known phase (e.g. `_off-2-foo.lua`). Patches do not auto-clean their effects on next boot — KOReader simply does not reapply them.
|
|
107
|
+
|
|
108
|
+
Patches that allocate scheduled tasks or register listeners must clean up on `CloseDocument` / `Exit` if the user might disable them mid-session. Easier path: design patches to be idempotent and applied only at startup.
|
|
109
|
+
|
|
110
|
+
## Failure mode
|
|
111
|
+
|
|
112
|
+
A patch that errors during application aborts only that patch; remaining patches and the rest of boot continue. The traceback lands in `crash.log` with the patch filename. Always test with debug logging on (`./kodev run --debug`) before shipping.
|
|
113
|
+
|
|
114
|
+
## When NOT to use a patch
|
|
115
|
+
|
|
116
|
+
- Adding substantial new UI → write a plugin.
|
|
117
|
+
- Anything users should be able to toggle → write a plugin.
|
|
118
|
+
- Patching something that has an official extension hook → use the hook (events, dispatcher, `addToMainMenu`).
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Plugin Anatomy
|
|
2
|
+
|
|
3
|
+
## Directory layout
|
|
4
|
+
|
|
5
|
+
A plugin is a directory whose name matches `.+%.koplugin`. KOReader scans `DEFAULT_PLUGIN_PATH` (the bundled `plugins/` directory) plus any path listed in the `extra_plugin_paths` setting. The user setting `plugins_disabled` is a table of plugin names to skip.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
MyPlugin.koplugin/
|
|
9
|
+
├── _meta.lua # plugin metadata (required for menu integration)
|
|
10
|
+
├── main.lua # returns the plugin class
|
|
11
|
+
├── README.md # optional, surfaced in the plugin manager UI
|
|
12
|
+
├── i18n/ # optional Crowdin-style translations
|
|
13
|
+
└── *.lua # additional sub-modules
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## `_meta.lua`
|
|
17
|
+
|
|
18
|
+
Returns a plain table that pluginloader reads before instantiating `main.lua`. Minimum fields:
|
|
19
|
+
|
|
20
|
+
```lua
|
|
21
|
+
return {
|
|
22
|
+
name = "myplugin", -- internal id, must match WidgetContainer name
|
|
23
|
+
fullname = _("My Plugin"), -- shown in plugin manager
|
|
24
|
+
description = _([[One-paragraph description.]]),
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Optional fields recognized by the loader: `version`, `author`. The translation function `_` comes from `gettext` and is available in `_meta.lua` evaluation scope.
|
|
29
|
+
|
|
30
|
+
## `main.lua` skeleton
|
|
31
|
+
|
|
32
|
+
```lua
|
|
33
|
+
local WidgetContainer = require("ui.widget.container.widgetcontainer")
|
|
34
|
+
local InfoMessage = require("ui.widget.infomessage")
|
|
35
|
+
local UIManager = require("ui.uimanager")
|
|
36
|
+
local _ = require("gettext")
|
|
37
|
+
|
|
38
|
+
local MyPlugin = WidgetContainer:extend{
|
|
39
|
+
name = "myplugin",
|
|
40
|
+
is_doc_only = false, -- true: only load inside ReaderUI
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function MyPlugin:init()
|
|
44
|
+
self.ui.menu:registerToMainMenu(self)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
function MyPlugin:addToMainMenu(menu_items)
|
|
48
|
+
menu_items.myplugin = {
|
|
49
|
+
text = _("My plugin"),
|
|
50
|
+
sorting_hint = "tools",
|
|
51
|
+
callback = function()
|
|
52
|
+
UIManager:show(InfoMessage:new{ text = _("Hello from MyPlugin") })
|
|
53
|
+
end,
|
|
54
|
+
}
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
return MyPlugin
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Key conventions:
|
|
61
|
+
|
|
62
|
+
- `name` must be unique and match the directory stem.
|
|
63
|
+
- `is_doc_only = true` restricts the plugin to ReaderUI; omit for both hosts.
|
|
64
|
+
- `init` runs after the host UI built `self.ui`. Treat it as the plugin's constructor — register menus, dispatcher actions, and event listeners here.
|
|
65
|
+
- Returning the class (not an instance) lets pluginloader instantiate per host.
|
|
66
|
+
|
|
67
|
+
## Lifecycle and host wiring
|
|
68
|
+
|
|
69
|
+
`pluginloader` instantiates plugins for the active UI host (`FileManager` or `ReaderUI`). Each instance is appended as a child of that host's `WidgetContainer`, so it receives every event the host receives via standard propagation.
|
|
70
|
+
|
|
71
|
+
To register hooks beyond the menu:
|
|
72
|
+
|
|
73
|
+
- Dispatcher actions (gestures, profiles): `Dispatcher:registerAction("my_action", { category = "none", event = "MyAction", title = _("My action"), general = true })` then handle `onMyAction`.
|
|
74
|
+
- Background work: extend `ui.plugin.background_task_plugin`.
|
|
75
|
+
- Toggle behavior: extend `ui.plugin.switch_plugin`.
|
|
76
|
+
|
|
77
|
+
## Event handlers in plugins
|
|
78
|
+
|
|
79
|
+
Implement `onFoo(self, ...)` to handle event `Foo`. The host UI broadcasts events down the tree; plugins receive them after host modules. Returning `true` stops propagation. Avoid swallowing events the reader needs (`PageUpdate`, `PosUpdate`) unless that is the goal.
|
|
80
|
+
|
|
81
|
+
## Persistence
|
|
82
|
+
|
|
83
|
+
- App-wide settings: `local LuaSettings = require("luasettings"); local s = LuaSettings:open(DataStorage:getSettingsDir() .. "/myplugin.lua")`.
|
|
84
|
+
- Per-document state: `self.ui.doc_settings:saveSetting("myplugin_state", value)`. Saved with the document's metadata.
|
|
85
|
+
- Always handle the case where the file does not yet exist; LuaSettings degrades to an empty table.
|
|
86
|
+
|
|
87
|
+
## Internationalization
|
|
88
|
+
|
|
89
|
+
Wrap user-visible strings: `local _ = require("gettext"); _("Save")`. For plurals, `gettext.ngettext("%1 page", "%1 pages", n)`. Translations live under KOReader's main `l10n/` tree; for in-tree plugins, use the project's translation pipeline. For out-of-tree plugins, you may ship strings English-only or vendor your own gettext catalog.
|
|
90
|
+
|
|
91
|
+
## Loading errors
|
|
92
|
+
|
|
93
|
+
Errors during plugin load disable the plugin silently for the user but log to `crash.log`. Defensive techniques:
|
|
94
|
+
|
|
95
|
+
- Wrap optional dependencies (e.g. plugins that integrate with another plugin) in `pcall(require, "...")`.
|
|
96
|
+
- Keep `_meta.lua` side-effect free — it loads in a plain `dofile` context.
|
|
97
|
+
- Avoid top-level `require` of modules that only exist in newer KOReader versions; gate with `pcall` and fall back gracefully.
|