luvus-module
GitHub用于编写 luvus AI 编码代理扩展模块的技能。支持通过 toml 配置侧边栏、状态栏、事件钩子及右键菜单,利用外部脚本与 luvus CLI 交互以增强功能。
Trigger Scenarios
Install
npx skills add RizRiyz/luvus --skill luvus-module -g -y
SKILL.md
Frontmatter
{
"name": "luvus-module",
"description": "Write a luvus module (an extension for luvus, mission control for your AI coding agents). Use when the user is building or debugging a luvus module: authoring luvus-module.toml, adding a sidebar dock, Luvus Bar widget, right-click action, event hook, module pane, module settings, or calling luvus back over its UHP."
}
Writing a luvus module
A luvus module is a plugin for luvus, mission control for your AI coding agents. There is no SDK and no scripting engine: a module is a directory with a luvus-module.toml manifest that declares argv commands (any executable: sh, python, node, a compiled binary). luvus runs those commands as subprocesses with LUVUS_* context in the environment, and the command calls luvus back through the same UHP the luvus CLI uses. So a module in bash/python/node can do what a built-in feature does.
Reference material in this repo (read for depth): MODULE-GUIDE.md, docs/13-modules.md, and the worked modules in examples/modules/ (branch-dock = a sidebar dock, agent-ping = an event hook, scratch-pane = a pane).
The manifest: luvus-module.toml
Top-level keys (all required unless noted):
id = "yourname.my-module" # unique, reverse-dns style
name = "My Module"
version = "0.1.0"
min_luvus_version = "0.8.3" # oldest luvus this needs
description = "One line." # optional
platforms = ["macos", "linux"] # optional; omit = all. Also per-item.
Then any of these tables, each declaring an argv command (a list, run as-is, cwd = the module dir):
[[docks]]id,title,placement(sidebar.left|sidebar.right) — reserve a sidebar dock. luvus renders it; you fill it withui.dock.push(see below).[[bars]]id,title,region(top-right|bottom-right), optionalpriority— reserve a bounded one-row Luvus Bar widget. Publish structured segments withluvus bar push(ui.bar.pushon the UHP); live content is not persisted.[[startup]]command— run once when the session is up and the socket is listening (and on enable). Dock rows and bar content are not persisted, so this is how you repaint them after a restart.[[events]]on,command— run when a luvus event fires. Validonvalues includeworkspace.created/closed,tab.created/closed/moved,pane.created/closed/forked/moved,pane.agent_status_changed,agent.hook, and thetask.*/lease.*events (seeKNOWN_EVENTSinsrc/module/manifest.rsfor the full set — an unknownonis a hard manifest error).[[actions]]id,title,command, optionalcontexts— a runnable action. Withcontexts = ["pane"|"workspace"|"node"|"agent"|"tab"]it also appears in that right-click menu, acting on what was clicked. Withoutcontextsit is CLI-only (luvus module run <id> <action>). Dock rows also invoke an action on click.[[panes]]id,title,command,placement(split|overlay|tab) — a real pane running your command (luvus module pane open <id> <entrypoint>).[[settings]]key,title,type(bool|string|number|enum), plusdefault,options(enum),min/max/step(number),secret(mask + hide the value). Rendered in Settings → Modules; values reach every command as env (below).[worktree_provider]one optional fixed creationcommand, optional fixedremove_command, and optionalplatforms. The user selects the module id inconfig.jsonatworktree.provider; config cannot replace either command.
A worktree provider reads one versioned JSON request from stdin. Creation gets
version, operation, repository, branch, and branch_exists, and must
print only {"path":"/absolute/worktree/path"} on stdout. Removal gets
version, operation, repository, path, branch, and force; it must
print nothing, exit successfully, and unregister and remove the target. If a
provider violates its exit or stdout contract after deletion already completed,
Luvus reconciles the proven Git/filesystem state instead of retaining a stale
workspace; stdout remains unsupported. Human logs go to stderr. This command is
synchronous and must not call back into the Luvus CLI/API while the server
waits. Luvus verifies the returned path is a
registered worktree of the source repository on the exact requested branch
before opening a workspace; explicit removal is verified before and after the
command. Internal rollback and task merge remain Git-backed.
What your command receives (no JSON parsing needed)
luvus puts context in the environment, flat, so a bash module never parses JSON:
LUVUS_MODULE_ID,LUVUS_MODULE_ROOT(the module dir),LUVUS_MODULE_VERSIONLUVUS_MODULE_TOKEN— UI-publisher credential for this server; the Luvus CLI forwards it automatically, so do not persist or override itLUVUS_MODULE_CONFIG_DIR,LUVUS_MODULE_STATE_DIR— writable per-module dirsLUVUS_WORKSPACE_ID,LUVUS_WORKSPACE_CWD,LUVUS_TAB_INDEXLUVUS_PANE_ID,LUVUS_PANE_CWD,LUVUS_PANE_AGENT,LUVUS_PANE_STATUS(the clicked/target pane)LUVUS_SETTING_<KEY>for each declared setting (uppercased key), plus the whole set as JSON[worktree_provider]commands read their versioned operation request as JSON from stdin- Dock-row clicks add
LUVUS_MODULE_DOCK_ID,LUVUS_MODULE_ROW_ACTION,LUVUS_MODULE_ROW_VALUE,LUVUS_MODULE_ROW_TEXT,LUVUS_MODULE_ROW_INDEX - Bar-segment clicks add
LUVUS_MODULE_BAR_ID,LUVUS_MODULE_BAR_SEGMENT, and optionalLUVUS_MODULE_BAR_VALUE LUVUS_MODULE_CONTEXT_JSON— the full snapshot, if you want structured dataLUVUS_SOCKET_PATH,LUVUS_BIN_PATH— to call luvus back (below)
What your command can do: call luvus back
Run the luvus CLI from inside the command; it talks to the running server over $LUVUS_SOCKET_PATH. Use "$LUVUS_BIN_PATH" to guarantee the same binary as the session. Module-facing methods:
luvus ui dock push --id <dock> --rows <json>(or pipe the JSON on stdin) — fill your dock. Rows are{text, dot?, tone?, spans?, action?, value?, menu?}; a row'sactioninvokes one of your[[actions]]on click, with the row'svalueinLUVUS_MODULE_ROW_VALUE.toneis a Luvus Bar tone name (normal,muted,accent,success,warning,error) for the text, andspansis[{text, tone?}]to color segments of one row with the same tone names (a span without a tone takes the row's). Both are optional and theme-bound, raw colors and ANSI are not accepted. Omittingtonegives the default row color, which is not the same asnormal.luvus bar push --id <widget> --content <json>— atomically publish structuredtext,symbol,state,badge,progress,spacer, andseparatorsegments. Add--compact-content; anactionmust name one of your[[actions]].luvus ui notification push --text <text> --level info|success|warning|error— publish bounded transient status; use--dedupe-keyfor replacement.luvus ui toast "<text>"— flash a one-line confirmation.luvus ui agent-title push --titles <json>— publish AGENTS sidebar titles for live panes (pane) and resumable sessions (built-inagent+session_id). OSC still wins; never send a Luvus=aliasas the title.luvus ui agent-title cleardrops one key or every title owned by your module. The CLI authenticates ownership automatically.luvus ui sidebar/ui dock list/ui dock move— sidebar/dock control.luvus tab rename <name>/tab list— tabs.luvus module settings <id>/module settings <id> <key>— read your settings exactly (values are masked insettings listwhensecret, but exact insettings get).- Plus the whole CLI:
luvus pane split/run/send,luvus agent ...,luvus git ..., etc.
Recipes
A sidebar dock (like examples/modules/branch-dock): declare [[docks]], a [[startup]] that runs a script which builds a JSON rows array and calls ui dock push, and [[events]] so it refreshes on workspace.created/tab.created. Give rows an action matching a [[actions]] id to make them clickable.
A Luvus Bar widget (like examples/modules/ci-bar): declare [[bars]], publish full and compact structured content from a one-shot startup/event action, and keep expensive work outside the render path. Use a pane for arbitrary terminal UI and a dock for multi-row content.
An event hook (like agent-ping): a [[events]] with on = "pane.agent_status_changed" and a command that reads LUVUS_PANE_* and reacts (notify, log, ui toast).
A right-click action: an [[actions]] with contexts = ["pane"] (or workspace/node/agent/tab). It appears in that menu and runs against the clicked target's LUVUS_* env.
A pane: a [[panes]] entry; open it with luvus module pane open <module-id> <entrypoint>.
Test loop
luvus module link <path-to-module-dir> # register a local module (dev)
luvus module list # confirm it's runnable
luvus module log [<id>] # tail command output/errors while iterating
luvus module run <id> <action> # invoke an action directly
luvus module enable <id> | disable <id>
Iterate: edit the script, re-run the action or trigger the event, watch module log. The manifest is validated on link; a broken manifest keeps the entry visible but not runnable, with the reason in module info <id>.
Conventions
- One module, one job. Keep commands fast and quiet — luvus caps a command's output (64 KiB) and runs at most 32 at a time.
- Identity env (
LUVUS_MODULE_ID,LUVUS_MODULE_TOKEN, socket path) is injected and cannot be overridden. Never log or persist the token. - Use
platformson the manifest or any item to skip a build step / hook / pane / action where it does not apply. - To share it:
luvus module install <owner>/<repo>clones + builds + registers from GitHub; tag the repo with theluvus-moduletopic soluvus module searchfinds it.
Version History
-
51fcfe1
Current 2026-09-22 00:44
新增模块创建/移除提供者功能;允许模块发布代理会话标题。
-
ee169da
2026-09-08 19:06
feat(dock): 为模块侧边栏行添加了可选的 tone 和 spans 属性 (#266)
- 9ee6401 2026-08-27 09:04


