supernote-plugin-dev skill
Build, debug, and extend Supernote e-ink device plugins using the sn-plugin-lib SDK (React Native + Android). Generic skill for ANY Supernote plugin project — not tied to a specific plugin. Trigger this skill whenever the user mentions Supernote, sn-plugin-lib, PluginManager, PluginCommAPI, PluginFileAPI, PluginNoteAPI, PluginDocAPI, .snplg files, e-ink plugin development, PluginHost, lasso operations, EMR coordinates, or wants to create/modify/debug a plugin for the Supernote NOTE or DOC apps. Even if the user just says 'plugin for my notebook' or 'extend my note-taking app' in the context of Supernote hardware, use this skill. If the current repo also has a project-specific Supernote skill (e.g. named after the plugin itself), read that one FIRST for this-repo facts, then use this skill for the generic SDK/workflow knowledge it doesn't repeat.
- GitHub stars
- 7
- Files
- 10
- Last commit
- Sep 2, 2026
- License
- MIT
- Allowed tools
- —
What does the supernote-plugin-dev skill do?
supernote-plugin-dev is an agent skill. Build, debug, and extend Supernote e-ink device plugins using the sn-plugin-lib SDK (React Native + Android). Generic skill for ANY Supernote plugin project — not tied to a specific plugin. Trigger this skill whenever the user mentions Supernote, sn-plugin-lib, PluginManager, PluginCommAPI, PluginFileAPI, PluginNoteAPI, PluginDocAPI, .snplg files, e-ink plugin development, PluginHost, lasso operations, EMR coordinates, or wants to create/modify/debug a plugin for the Supernote NOTE or DOC apps. Even if the user just says 'plugin for my notebook' or 'extend my note-taking app' in the context of Supernote hardware, use this skill. If the current repo also has a project-specific Supernote skill (e.g. named after the plugin itself), read that one FIRST for this-repo facts, then use this skill for the generic SDK/workflow knowledge it doesn't repeat.
How to install the supernote-plugin-dev skill
Each command copies the skill folder into the client's skills directory. Restart the client or start a new session to load it.Install the supernote-plugin-dev skill in Plugin
/plugin marketplace add gorlix/supernote-plugin-dev /plugin install supernote-plugin-dev@supernote-plugin-dev
Install the supernote-plugin-dev skill in Claude Code
~/.claude/skills/supernote-plugin-dev/SKILL.md
$ npx degit gorlix/supernote-plugin-dev/skills/supernote-plugin-dev ~/.claude/skills/supernote-plugin-devInstall the supernote-plugin-dev skill in Claude Code (project)
.claude/skills/supernote-plugin-dev/SKILL.md
$ npx degit gorlix/supernote-plugin-dev/skills/supernote-plugin-dev .claude/skills/supernote-plugin-devInstall the supernote-plugin-dev skill in Codex
~/.agents/skills/supernote-plugin-dev/SKILL.md
$ npx degit gorlix/supernote-plugin-dev/skills/supernote-plugin-dev ~/.agents/skills/supernote-plugin-devInstall the supernote-plugin-dev skill in Gemini CLI
~/.gemini/skills/supernote-plugin-dev/SKILL.md
$ npx degit gorlix/supernote-plugin-dev/skills/supernote-plugin-dev ~/.gemini/skills/supernote-plugin-devInstall the supernote-plugin-dev skill in Claude.ai
npx degit gorlix/supernote-plugin-dev/skills/supernote-plugin-dev supernote-plugin-dev && zip -r supernote-plugin-dev.zip supernote-plugin-dev # claude.ai → Customize → Skills → + → Create skill → Upload a skill → supernote-plugin-dev.zip
SKILL.md
View on GitHubSupernote Plugin Development Skill
You are an expert Supernote plugin developer. Supernote plugins extend the NOTE (handwriting notebook) and DOC (document reader) apps on Supernote e-ink devices. Plugins run inside a PluginHost process that provides a React Native runtime, and communicate with NOTE/DOC via AIDL + SDK interfaces.
This skill is deliberately project-agnostic — it applies to any Supernote plugin. If the repo you're working in has its own project-specific skill (check .claude/*/SKILL.md for one named after the plugin), read that first for this-repo facts (pluginID, architecture, known pitfalls specific to that codebase); come back here for generic SDK/workflow/gotcha knowledge.
📦 SDK version
Check the plugin's own package.json for the pinned sn-plugin-lib version before relying on any
version-gated fact below — don't assume 0.1.65 is what a given repo uses. Notable SDK changes
between 0.1.19 and 0.1.65 that affect the reference docs below (verify against your repo's
actual pinned version and the MCP before trusting these):
PluginManager.addPluginLifeListener → registerPluginLifeListener (breaking, different callback
shape), PluginDocAPI.generateDocImage → generateCurrentDocImage (renamed + new type param),
PointUtils.getNotePageSize removed (no replacement), new hasPermission/requestPermission
runtime permission gate (see Pattern 17 in references/patterns.md), and new current-file
page-element CRUD (modifyPageElements/insertPageElements/deletePageElements/
batchUpdatePageElements on PluginCommAPI).
react-native is pinned to 0.79.2 for every plugin, hard, by the host device itself — not just
a per-project convention. Supernote's own docs state: "The plugin framework uses React Native
0.79.2. Your plugin project must use the same version; otherwise it may fail to run or be
incompatible with the host." Do not let a plugin's react-native drift from 0.79.2, and treat
any request to bump it as blocked-by-design until Supernote itself updates the on-device host
runtime — re-check the supernote-docs MCP periodically rather than assuming this is stale.
A React react-native-renderer version mismatch crashes silently on-device, not in npm test.
react-native@0.79.2 bundles its own react-native-renderer built against react@19.0.0 exactly.
npm's peer range on react-native (^19.0.0) is loose enough to let npm install pull a newer
react (e.g. 19.2.x) without any warning, but at runtime React throws Incompatible React versions followed by TypeError: Cannot read property 'default' of undefined before the first
render — the UI never appears and there's no error visible to the user. If react-native stays
pinned at 0.79.2, keep react/react-test-renderer/@types/react pinned at exactly 19.0.0
too, regardless of what npm outdated suggests. Verify any react bump by watching adb logcat
on a real device (or emulator) before trusting npm test alone.
⚠️ Authoritative source: the supernote-docs MCP
A live documentation MCP (supernote-docs) should be configured for any serious Supernote plugin
work and MUST be used for any API/signature question — if it isn't configured in the current
project yet, add it: claude mcp add --transport http --scope project supernote-docs https://docs.supernote.com/mcp (then approve it in an interactive session; it can't self-approve
in a non-interactive one). It has three tools: search_supernote (semantic search),
query_docs_filesystem_supernote (rg/cat/tree over the .mdx docs), and submit_feedback
(report a doc bug back to Supernote). The live docs are the authoritative, up-to-date source.
The local references/*.md files in this skill are a useful supplement but may lag — when they
disagree with the MCP, trust the MCP. Query the MCP before writing any SDK code, and especially
before assuming any version-pin, permission, or compatibility fact carried over from a previous
project or an older revision of these reference files.
Before You Start
Always read the appropriate reference file(s) before writing code (and cross-check signatures
against the supernote-docs MCP, which is authoritative):
| Task | Read first |
|---|---|
| Any API call or type question | MCP supernote-docs → then references/api-quick-ref.md |
| New project / environment / build / deploy / debug | references/setup-and-build.md |
| Common recipes (lasso ops, coordinate conversion, pending button, permissions, etc.) | references/patterns.md |
| Type definitions (Element, Stroke, Geometry, TextBox, etc.) | references/types.md |
| i18n, multi-language buttons | references/i18n.md |
| Floating window overlay | references/floating-window.md |
| Pen lasso, EMR pen disable, scoped pen lock | references/pen-emr.md |
| SQLite local storage in plugins | references/sqlite.md |
| Preparing to submit a plugin for review / publish | references/publish-review.md |
The reference files contain authoritative API signatures and constraints gathered from real plugin builds — do not rely on memory alone; the live MCP wins on any conflict.
Architecture (30-second overview)
┌─────────────┐ AIDL ┌─────────────┐ SDK (TurboModule) ┌──────────┐
│ NOTE / DOC │ ◄──────────► │ PluginHost │ ◄──────────────────────► │ Plugin │
│ (Host App) │ │ (RN Runtime) │ │(Your Code)│
└─────────────┘ └─────────────┘ └──────────┘
- Plugin: Your React Native code. Entry =
index.js(init + buttons) +App.tsx(UI). - PluginHost: Loads, schedules, and renders plugins. Provides the RN runtime.
- NOTE/DOC: Host apps. Show plugin buttons in toolbar / lasso toolbar / text-selection toolbar.
Communication: Plugin → SDK (sn-plugin-lib) → TurboModule → Java → C/C++ → NOTE/DOC file operations.
Plugin Lifecycle
- Install:
.snplgcopied toMyStyle/, user installs via Settings → Apps → Plugins - Init: PluginHost starts RN env → executes
index.js→PluginManager.init()→ button registration - Event: User taps plugin button → AIDL event → PluginHost → plugin listener callback
- UI: If
showType=1, PluginHost rendersApp.tsxin a full-screen container - API calls: Plugin calls
PluginCommAPI/PluginFileAPI/PluginNoteAPI/PluginDocAPI - Close:
PluginManager.closePluginView()or user navigates away
Development Workflow
When the user wants to create a new plugin:
- Scaffold:
npx @react-native-community/cli init <n> --template @supernote-plugin/sn-plugin-template --version 0.79.2 - Init in
index.js:PluginManager.init()afterAppRegistry.registerComponent(...) - Register buttons:
PluginManager.registerButton(type, appTypes, config)— type 1=toolbar, 2=lasso, 3=text-selection(DOC only) - Write UI in
App.tsxusing React Native components - Call SDK APIs as needed:
PluginCommAPI,PluginFileAPI,PluginNoteAPI,PluginDocAPI - Build: In project root, run
.\buildPlugin.ps1(PowerShell) or./buildPlugin.sh(bash) - Deploy:
adb push build\outputs\<n>.snplg /storage/emulated/0/MyStyle/→ install on device - Debug:
adb logcat -c→ trigger action → wait 10s →adb logcat -d -s ReactNativeJS:V
Critical Constraints (memorize these)
Coordinate Systems
- EMR coordinates: Hardware pen sampling coords, higher precision. Used for stroke points, Element.maxX/maxY.
- Pixel coordinates: Screen pixels (left-top origin). Used for Rect params, lasso, geometry insertion, UI layout.
- Conversion:
PointUtils.androidPoint2Emr(point, pageSize)/emrPoint2Android(…). Get pageSize fromPluginFileAPI.getPageSize(path, page). Seeapi-quick-ref.md §6for supported sizes. - Which APIs use which? Pixel:
insertGeometry,insertFiveStar,insertText(textRect),lassoElements,getLassoRect,resizeLassoRect, Title/TextBox/Picture/Geometry fields. EMR:Stroke.points,FiveStar.points(stored),Element.maxX/maxY.
Layer Restrictions
- Main layer (layer=0): Supports ALL element types.
- Custom layers (layer 1-3): Only strokes, pictures, text boxes, and geometry. NO titles, links, or five-stars.
- DOC files: Only have one layer (main). Cannot insert text boxes, titles, or links.
Lasso Context
- Many APIs (
getLassoElements,getLassoRect,modifyLassoText,setLassoTitle, etc.) require an active lasso context — the user must have lasso-selected something first. modifyLassoTextandmodifyLassoLinkonly work when exactly one element of that type is selected.setLassoBoxState(2)= permanently removes the lasso. Use only when the operation is done.setLassoBoxState(3)(0.1.43+) = hides all lasso UI but preserves the lasso state internally.
Element & ElementDataAccessor
Elementis the universal data structure for all visible items (strokes, titles, links, text boxes, geometry, pictures, five-stars).- Large data (angles, contours, stroke points) uses
ElementDataAccessor— a lazy accessor, NOT a full array. Callsize(),get(index),getRange(start, end)to fetch data on demand. - Always call
element.recycle()when done to free native-side memory. - Always call
PluginCommAPI.createElement(type)before inserting new elements — this creates the native-side cache and accessor references.
API Response Pattern
All async APIs return APIResponse<T>:
{ success: boolean; result?: T; error?: { message: string } }
Always check success before reading result.
PluginConfig.json
pluginKeyMUST match the first argument ofAppRegistry.registerComponent(...). Mismatch = plugin won't load.pluginIDis auto-generated on first build. Never change it after distribution — it identifies the plugin.nameis what shows in Settings → Apps → Plugins. It is not auto-derived from the in-app title string — a plugin can easily ship with a friendly in-app title while the install list still shows the raw npm-style package slug (e.g.sn-my-plugin) becausenamewas never edited from its scaffold default. Keep it in sync with whatever the plugin actually calls itself.iconPath(relative path to project root, e.g.assets/icon.png) is not set by the scaffold script — it must be added manually, same asauthor. Without it, the plugin ships with no custom icon in the install list (silently — no build warning).buildPlugin.shcopies the file and rewritesiconPathto/<filename>in the packaged config; check the generatedbuild/outputs/*.snplg'sPluginConfig.jsonto confirm it actually landed.uses-permissions(string array, e.g.["plugin.permission.INTERNET"]) must list every permission the plugin will ever callhasPermission/requestPermissionfor — see Pattern 17 inreferences/patterns.md. Not set by the scaffold either; add manually.
Build, Deploy & Debug
See references/setup-and-build.md for full details. Quick commands:
.\buildPlugin.ps1 # build → build/outputs/<n>.snplg
adb push build\outputs\*.snplg /storage/emulated/0/MyStyle/ # deploy
adb logcat -c; Start-Sleep 10; adb logcat -d -s ReactNativeJS:V # debug
Key log tags: ReactNativeJS (console.log), PluginHost (lifecycle), SNPlugin (SDK native ops).
Decision Tree: Which API Module?
What do you need to do?
│
├─ Manage plugin lifecycle, buttons, events, device info, touch events
│ → PluginManager (references/api-quick-ref.md §1) — includes registerMotionListener (0.1.43+),
│ registerPluginLifeListener (0.1.65+, replaces addPluginLifeListener)
│
├─ Check or request access to Document/Note/MyStyle/etc. outside the plugin's own dir
│ → PluginManager.hasPermission / requestPermission (references/api-quick-ref.md §1
│ "Permissions") + patterns.md Pattern 17 (0.1.65+)
│
├─ Work with current page context (lasso, stickers, geometry, reload)
│ → PluginCommAPI (references/api-quick-ref.md §2)
│
├─ Modify/insert/delete elements on the currently-open file (no filePath needed)
│ → PluginCommAPI.modifyPageElements / insertPageElements / deletePageElements /
│ batchUpdatePageElements (references/api-quick-ref.md §2, 0.1.65+) — call
│ getPageDisplaySize() first, not PluginFileAPI.getPageSize
│
├─ Operate on file data (pages, elements, layers, templates, keywords)
│ → PluginFileAPI (references/api-quick-ref.md §3)
│
├─ NOTE-specific features (text, titles, links, images, save)
│ → PluginNoteAPI (references/api-quick-ref.md §4)
│
├─ DOC-specific features (selected text, page text)
│ → PluginDocAPI (references/api-quick-ref.md §5)
│
├─ Route lasso/toolbar buttons to different screens without showing main panel
│ → Pending Button ID pattern (references/patterns.md Pattern 5)
│
├─ Show a persistent overlay that survives closePluginView()
│ → Native Floating Window (references/patterns.md Pattern 6)
│
├─ Disable the EMR pen during a plugin-driven gesture (e.g. pen lasso on overlay)
│ so strokes don't leak into the .note file
│ → Scoped Pen Disable (references/patterns.md Pattern 16) + see Pattern 15 for
│ architecture and the PluginApp.showPluginView reflection release recipe
│
├─ Insert text sequentially across pages (e.g. streamed from phone/AI)
│ → Page-Anchored Sequential Insertion (references/patterns.md Pattern 13)
│
├─ OCR-recognise handwritten strokes / text boxes into a string
│ → PluginCommAPI.recognizeElements(elements, pageSize) (references/api-quick-ref.md §2)
│ 1. getLassoElements() to get the Element array
│ 2. getCurrentFilePath() + getCurrentPageNum() + getPageSize(path, page) for the full page size
│ 3. recognizeElements(elements, pageSize) → APIResponse<string>
│ 4. cancelRecognize() to abort a long-running recognition if needed
│
├─ Extract hardcoded strings / add multi-language support (i18n)
│ → references/i18n.md Pattern 7 (JSON button name) + Pattern 10 (registerLangListener)
│ → Generic extract-translate-convert workflow for new locales: patterns.md Pattern 12
│
└─ Ready to submit the plugin for review / publish, unsure if it'll pass
→ references/publish-review.md — self-audit checklist derived from Supernote's
published review process (permissions, file/data ops, network disclosure, description
accuracy)
Files in this skill
10supernote-plugin-dev skill questions
How do I install the supernote-plugin-dev skill in Claude Code?
Run npx degit gorlix/supernote-plugin-dev/skills/supernote-plugin-dev ~/.claude/skills/supernote-plugin-dev. Claude Code loads skills from ~/.claude/skills (all projects) or .claude/skills (one project) and uses a skill when its description matches the task.
Which clients can use the supernote-plugin-dev skill?
Any client that reads the Agent Skills format: Claude Code, Codex, Gemini CLI and claude.ai (upload as a ZIP). Scripts in the skill run only where the client allows code execution.