Community코딩 & 개발github.com

aknahin/koplugin-skill

KOReader 플러그인(*.koplugin, Lua)을 빌드, 검토, 디버그 및 검증합니다. KOReader, koplugin, e-리더 플러그인 관련 요청 시 사용하세요.

koplugin-skill란 무엇인가요?

이 스킬은 KOReader 플러그인 개발을 위한 전문 도구로, Lua(LuaJIT) 기반의 KOReader 전자책 리더 플랫폼에서 작동하는 플러그인을 생성, 검토, 디버그 및 검증합니다. Kobo, Kindle, PocketBook, Android 기기와 데스크톱 에뮬레이터를 지원하며, 사용자가 KOReader 플러그인(_meta.lua, WidgetContainer, UIManager 등)을 언급하거나 새로운 기능/메뉴 항목/제스처 액션을 추가하려 할 때 자동으로 활성화됩니다. 이 스킬은 스캐폴딩 도구와 정적 검증기를 포함하여 플러그인 구조, LuaJIT 문법, luacheck 검사를 자동으로 수행하므로 개발자가 API를 추측하지 않고도 올바른 규칙을 따를 수 있습니다. 공식 문서와 실제 플러그인 소스를 참조 자료로 활용하며, 생성된 코드는 항상 검증기를 통과해야 한다고 강조합니다.

지원 대상~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/aknahin/koplugin-skill/tree/HEAD/skills/koplugin-skill

Installed? Explore more 코딩 & 개발 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

KOReader 플러그인 개발

KOReader의 프론트엔드는 작은 위젯 툴킷 위에서 동작하는 일반 Lua(LuaJIT)입니다. 플러그인은 <name>.koplugin/ 디렉터리에 main.lua(코드)와 _meta.lua(표시 정보)로 구성됩니다. 빌드 단계가 없으므로, 올바른 방법은 아래의 검증된 규칙을 따르고 번들 스크립트로 확인하는 것이 API를 추측하는 것보다 빠릅니다.

공식 문서: https://koreader.rocks/doc/ (API 참조, 소스에서 생성됨) 및 소스 https://github.com/koreader/koreader (plugins/에 약 40개의 실제 플러그인; plugins/hello.koplugin이 최소 예제). 시그니처가 확실하지 않으면 모듈 문서 페이지를 확인하거나 소스를 읽으세요 — KOReader API는 릴리스마다 변경되며, 문서 사이트가 기억보다 우선합니다.

워크플로우

  1. 스캐폴딩 (새 플러그인): python3 <skill>/scripts/new_plugin.py <id> "<전체 이름>" [--dir <경로>] 명령어로 템플릿에서 <id>.koplugin/ 디렉터리를 생성합니다. 메뉴 항목, Dispatcher 액션, 설정 지속성, 생명주기 훅이 미리 연결되어 있습니다. 처음부터 작성하지 말고 이를 편집하세요.
  2. 구현 은 이 파일의 패턴을 따르고, 필요한 경우에만 참조 파일을 열어보세요:
    • references/plugin-anatomy.md — 파일 레이아웃, 로더 규칙, is_doc_only, 초기화 순서, 메뉴 sorting_hint 값
    • references/ui-widgets.md — InfoMessage, ConfirmBox, InputDialog, MultiInputDialog, Menu, ButtonDialog, SpinWidget, Notification 및 작동 코드 조각
    • references/events-and-lifecycle.md — onXxx 핸들러, 브로드캐스팅, 스케줄링, Dispatcher, 리더 vs 파일 관리자 컨텍스트
    • references/settings-and-storage.md — LuaSettings, G_reader_settings, 책별 문서 설정, DataStorage 경로
    • references/testing-and-verification.md — 에뮬레이터 (./kodev run), luacheck, busted 스펙, 기기 설치 및 로그 읽기
    • references/pitfalls.md — KOReader를 충돌시키거나 아무것도 하지 않는 실수; 마무리 전에 훑어보세요.
  3. 검증 (항상, 작동한다고 말하기 전에): python3 <skill>/scripts/validate_plugin.py <path/to/x.koplugin> 명령어를 실행합니다. 구조, _meta.lua, 메뉴 연결, gettext 사용, LuaJIT 문법(luajit -bl), luacheck(도구가 있을 때)를 확인합니다. 모든 ERROR를 수정하고 모든 WARN을 읽으세요. 그런 다음 검증된 내용과 검증되지 않은 내용을 솔직히 말하세요 — 검증기는 런타임 동작을 증명할 수 없으며, 오직 에뮬레이터나 기기만이 이를 증명할 수 있습니다(테스트 참조 참고).

최소 실행 가능한 플러그인

-- _meta.lua
local _ = require("gettext")
return {
    fullname = _("My Plugin"),
    description = _([[Plugin management에 표시될 한 줄 설명.]]),
}
-- main.lua
local Dispatcher = require("dispatcher")
local InfoMessage = require("ui/widget/infomessage")
local UIManager = require("ui/uimanager")
local WidgetContainer = require("ui/widget/container/widgetcontainer")
local _ = require("gettext")

local MyPlugin = WidgetContainer:extend{
    name = "myplugin",        -- 고유 ID, 디렉터리 접두사와 일치
    is_doc_only = false,      -- true = 문서가 열려 있을 때만 로드됨
}

function MyPlugin:onDispatcherRegisterActions()
    Dispatcher:registerAction("myplugin_hello", {
        category = "none", event = "MyPluginHello",
        title = _("My Plugin: hello"), general = true,
    })
end

function MyPlugin:init()
    self:onDispatcherRegisterActions()
    self.ui.menu:registerToMainMenu(self)
end

function MyPlugin:addToMainMenu(menu_items)
    menu_items.myplugin = {
        text = _("My Plugin"),
        sorting_hint = "more_tools",
        callback = function() self:onMyPluginHello() end,
    }
end

function MyPlugin:onMyPluginHello()
    UIManager:show(InfoMessage:new{ text = _("Hello from my plugin") })
    return true  -- 이벤트 소비됨
end

return MyPlugin

중요한 규칙 (그리고 그 이유)

  • 사용자에게 보이는 모든 문자열을 _()로 감싸세요 — KOReader는 gettext를 통해 번역됩니다. 감싸지 않은 문자열은 절대 지역화될 수 없습니다. 문자열 보간에는 T = require("ffi/util").template을 사용하세요(T(_("페이지 %1"), n)). .. 연결은 언어에 따라 단어 순서가 다르므로 사용하지 마세요.
  • 이벤트는 on<이벤트 이름> 형식의 메서드입니다; 이벤트를 소비한 경우에만 true를 반환하고, 그렇지 않으면 다른 위젯(코어 KOReader 포함)이 이벤트를 받지 못합니다.
  • UI 스레드를 절대 차단하지 마세요. 네트워크 호출, 큰 파일 검색, os.execute는 단일 스레드 e-잉크 기기를 멈추게 합니다. 긴 작업에는 NetworkMgr:runWhenOnline, UIManager:scheduleIn, Trapper:wrap(하위 프로세스 + 진행률)을 사용하세요.
  • LuaSettings로 지속성을 유지하고 의도적으로 플러시하세요. 설정 파일은 DataStorage:getSettingsDir()에 저장됩니다. 변경 후(onFlushSettings에서도) :flush()를 호출하세요 — 그렇지 않으면 충돌이나 배터리 방전 시 설정이 손실됩니다.
  • 타이머(UIManager:unschedule(fn))와 onCloseWidget/onCloseDocument에서 연 위젯을 정리하세요. 누수된 스케줄된 콜백은 기기를 계속 깨우고 배터리를 소모합니다.
  • E-잉크 인식 UI: InfoMessage에 timeout을 사용하고, 애니메이션을 피하며, 변경된 위젯에만 최소한으로 새로 고침하세요(UIManager:setDirty(widget, "ui")).
  • logger를 사용하세요 (require("logger"); logger.dbg/info/warn/err). print 대신 사용하세요. 출력은 에뮬레이터의 stdout과 기기의 crash.log로 전달됩니다.
  • 선택적 종속성을 보호하세요 (self.ui.statistics, self.ui.document, Device:hasFrontlight()): 동일한 플러그인이 파일 관리자나 해당 기능이 없는 기기에서 로드될 수 있습니다.

결과 보고

생성하거나 변경한 내용, 정확한 검증 명령어와 그 결과, 그리고 검증되지 않은 부분(예: "에뮬레이터에서 실행되지 않음")을 명시하세요. 검증기나 luacheck를 사용할 수 없었다면, 깨끗하게 통과했다고 암시하지 말고 그렇게 말하세요.

관련 스킬