Plugin Config (plugin.toml)
Every plugin has a plugin.toml in its root folder. It tells N.E.K.O what your plugin is, how to load it, and what capabilities it has.
Below is a complete config for a fictional "Smart Notes" plugin. This plugin can search and create notes, has its own UI panel, supports multiple languages, and can be called by the AI agent.
Full example
[plugin]
id = "smart_notes"
name = "Smart Notes"
description = "Manage your notes: search, create, organize, with AI-powered classification."
short_description = "Note management with AI-powered organization."
keywords = ["note", "笔记", "memo", "record", "メモ"]
version = "1.2.0"
entry = "plugin.plugins.smart_notes:SmartNotesPlugin"
[plugin.author]
name = "Alice"
[plugin.sdk]
recommended = ">=0.1.0,<0.2.0"
supported = ">=0.1.0,<0.3.0"
[plugin.i18n]
default_locale = "zh-CN"
locales_dir = "i18n"
[plugin.store]
enabled = true
[plugin.ui]
enabled = true
[[plugin.ui.panel]]
id = "main"
title = "Smart Notes"
entry = "ui/panel.tsx"
context = "dashboard"
permissions = ["state:read", "action:call"]
[[plugin.ui.guide]]
id = "quickstart"
title = "User Guide"
entry = "docs/guide.md"
permissions = ["state:read"]
[plugin_runtime]
enabled = true
auto_start = true
[notes]
max_per_page = 20
auto_classify = trueSection by section
[plugin] — Who is this plugin
[plugin]
id = "smart_notes"
name = "Smart Notes"
entry = "plugin.plugins.smart_notes:SmartNotesPlugin"These three fields are required. id must match the folder name. entry tells the system where to find your Python class.
description = "Manage your notes: search, create, organize, with AI-powered classification."
short_description = "Note management with AI-powered organization."
keywords = ["note", "笔记", "memo", "record", "メモ"]These three fields determine whether the AI agent can find your plugin:
short_description— The AI uses this to judge "what can this plugin do". Keep it concise and accurate.keywords— The AI uses these to match user intent. If the user says "take a note" and your keywords include "note", it matches.description— Full description for humans, shown in Plugin Manager.
If your plugin doesn't need to be called by the AI (e.g. a pure listener), you can skip these and add passive = true.
version = "1.2.0"Optional. Used for version management and marketplace publishing.
[plugin.author] — Who wrote it
[plugin.author]
name = "Alice"Optional. Shown in Plugin Manager.
[plugin.sdk] — Which SDK version it's compatible with
[plugin.sdk]
recommended = ">=0.1.0,<0.2.0"
supported = ">=0.1.0,<0.3.0"Tells the system which SDK version your plugin was written for. If the user's N.E.K.O is too old or too new, the system will warn or refuse to load.
supported— Below this range: refused to loadrecommended— Best experience in this rangeuntested— Allowed but shows "untested" warningconflicts— Explicitly incompatible versions
[plugin_runtime] — How it runs
[plugin_runtime]
enabled = true
auto_start = true
timeout = 10
startup_failure = "warn"timeout- Number of seconds to wait for startup readiness; must satisfy0 < timeout <= 300. Omit it to use the system default.startup_failure- What to do iflifecycle.startupraises after the process is alive. Omit it to default towarn:warnkeeps the plugin running and marks startup as degraded,failaborts startup, andignoreonly logs the error.enabled— Set tofalseto temporarily disable without deleting filesauto_start— Whentrue, starts automatically with N.E.K.O; otherwise start manually from the panel
[plugin.i18n] — Multi-language support
[plugin.i18n]
default_locale = "zh-CN"
locales_dir = "i18n"If your plugin needs multiple languages, create an i18n/ folder in your plugin directory with locale files:
i18n/
├── en.json
└── zh-CN.jsonDon't need i18n? Just don't include this section.
[plugin.store] — Persistent storage
[plugin.store]
enabled = trueWhen enabled, you can use self.store in code to save and retrieve data (key-value pairs) that persists across restarts.
Don't need storage? Just don't include this section (disabled by default).
[plugin.ui] — Custom UI
[plugin.ui]
enabled = true
[[plugin.ui.panel]]
id = "main"
title = "Smart Notes"
entry = "ui/panel.tsx"
context = "dashboard"
permissions = ["state:read", "action:call"]
[[plugin.ui.guide]]
id = "quickstart"
title = "User Guide"
entry = "docs/guide.md"
permissions = ["state:read"]If your plugin needs a custom interface in Plugin Manager:
panel— Interactive panel (written in TSX, can have buttons, tables, forms)guide— Read-only documentation (written in Markdown)
The file extension determines rendering: .tsx = interactive panel, .md = documentation.
Don't need UI? Just don't include this section. See Hosted UI for details.
Custom sections — Your business config
[notes]
max_per_page = 20
auto_classify = trueAny section the framework doesn't recognize becomes your business config. Read it in code:
cfg = await self.config.dump()
notes_cfg = cfg.get("notes", {})
max_per_page = notes_cfg.get("max_per_page", 20)You can define as many custom sections as you want, named however you like.
Directory structure for this plugin
plugin/plugins/smart_notes/
├── plugin.toml ← the file above
├── __init__.py ← plugin code
├── i18n/ ← locale files (because [plugin.i18n] is configured)
│ ├── en.json
│ └── zh-CN.json
├── ui/ ← interactive panel (because [[plugin.ui.panel]] is configured)
│ └── panel.tsx
├── docs/ ← user guide (because [[plugin.ui.guide]] is configured)
│ └── guide.md
└── data/ ← runtime data (auto-created, self.data_path() points here)Only plugin.toml and __init__.py are required. Everything else is created as needed.
