ClaudeMods
☰
ZH-TW
● 0 人在線上 · 瀏覽 0 次
贊助提交作品
GitHub 儲存庫 · 發布者 DannyMac180

mode-registry

任何外掛都能提供模式的共用 /mode 開關。它會在提示列尾端顯示作用中的模式,不呼叫模型。

DannyMac180@DannyMac180

DannyMac180/skills/tree/main/modsmith/templates/mode-registry

已翻譯

關於這個 mod

mode-registry

一個 /mode 開關,任何外掛都可以向它提供模式。它會在提示列尾端顯示目前模式。永遠不呼叫模型。

這是「mod 組合」模式:只要安裝一次 registry;模型路由器、「artifact mode」,或這個模板旁邊的 effort modes,都可以把自己的模式加入其中。目前模式會以變暗的標籤顯示在提示列尾端(例如 review mode,和 focus 及其他模式並列),並可透過 /mode 切換。

它是註冊表,不知道任何模式具體做什麼,只維護清單和目前選擇。每個外掛讀取這個選擇,再改變自己的行為。

可以做什麼

| 輸入 | 結果 | | --- | --- | | /mode | 在提示列上方開啟選擇器:每個模式一個按鈕(快速鍵 1-9,0 表示關閉),另有 Close。點擊它或按 ctrl+x tab 將按鍵交給選擇器。 | | /mode review | 切換到 id 為 review 的模式。列尾顯示 review mode。 | | /mode off | 清除模式(none 和 clear 也可以)。 | | /mode list | 列出所有可用模式、提供它的外掛,以及目前啟用的模式。 |

作用中的模式存在工作階段的 $.state 中,不會跨工作階段保存(要保存也寫入 $.store)。registry 不會在 /clear 時重設它。引擎是否在 /clear 後保留 $.state 尚未確認。

成本

永遠不呼叫模型。 列尾標籤和選擇器只是 UI,不會進入上下文。每條 /mode 指令只在轉錄中增加一行,模型會讀取它(約 10-20 個 token;/mode list 每個模式約 15 個 token)。在選擇器中按鍵不會增加內容:它會靜默切換模式,因此模型不會被告知(想讓模型知道時使用 /mode <id>)。這行會加在快取前綴後面,因此不會破壞提示快取。某個模式本身的成本由提供它的外掛決定,例如 effort-modes 的切換會產生一次快取重建。

從你的外掛提供模式

契約位於 types/index.d.ts,包含這個外掛擁有的三個值:

  • mode-registry.catalog:ModeSpec[],所有可提供的模式
  • mode-registry.active:string | null,目前模式的 id
  • mode-registry.isPicking:boolean,選擇器是否開啟

只有 registry 會寫入這些值。你可以 hook registry 對 catalog 的寫入來提供模式:

on('state.set', { plugin: 'mode-registry', key: 'catalog' }, ($, e, next) =>
  next({
    ...e,
    value: [
      ...e.value.filter(m => m.id !== 'router'),
      { id: 'router', label: 'Router', description: 'Picks the model per turn', owner: 'my-router' },
    ],
  }),
)

然後在行為所在的位置讀取作用中的模式:

const { value: active = null } = await $.state.get({ plugin: 'mode-registry', key: 'active' })
if (active !== 'router') return next(e)

幾個細節很重要:

  • 加入自己的 id 前,先過濾自己的 id。registry 會在工作階段開始時和每次 /mode 時重寫 catalog;先過濾可避免自己的模式出現兩次。
  • 在實際執行行為的 hook 中讀取 active。不要複製到跨回合保留的模組變數:熱重載會清除模組變數,而在 ui.render 中讀取會讓你訂閱重繪。
  • 沒有安裝 registry 也不會出錯。版本 0 中 active 會讀到 undefined,state.set hook 不會觸發,外掛會執行預設行為。
  • 要從程式切換模式(例如自動路由器看到 diff 後切到 review),呼叫 $.command.run({ command: 'mode', args: 'review' })。它走相同路徑和相同 veto,並會排到工作階段閒置。
  • 要拒絕或重新導向切換,可對 { plugin: 'mode-registry', key: 'active' } 掛 state.set hook,再傳給 next 另一個 value。/mode 會回報實際落地的值,因此使用者能看見 veto。

做型別檢查時,在 plugin.json 列出依賴:"dependencies": ["mode-registry"]。引擎從資料夾載入外掛時,會把契約寫入 .claude-plugin/types/mode-registry/,你的 tsconfig.json 會讀取它(effort-modes 就是這樣做的)。注意:宣告這個依賴的外掛在沒有 registry 時完全不會載入。如果外掛也應該獨立運作,就不要寫依賴,而是在啟用 mode-registry 的工作階段中執行 /plugin-types。絕不要手動複製這個檔案。

組合方式

  • effort-modes(在這個模板旁邊):提供 ui、api 和 review。
  • 任何在 SessionMode 列尾或 AbovePrompt 區域繪製的外掛。registry 會把標籤加入 e.props.modes,把其他內容繼續傳遞,並在選擇器下方繪製 {await next(e)}。

安裝/載入

claude plugin validate templates/mode-registry
claude plugin test templates/mode-registry           # 4 tests, incl. a veto from a third plugin
claude --plugin-dir templates/mode-registry --plugin-dir templates/effort-modes

function hooks 仍是 early access。如果你的建置預設不載入 hooks 模組,請設定 CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1。這個設定會載入每個已安裝且提供 hooks 的外掛之 hooks 模組,不只這個外掛。

安裝

請先查看作者 README,確認 marketplace 與外掛名稱;指令可能隨儲存庫結構而變動。

claude plugin marketplace add DannyMac180/skills
claude plugin install mode-registry
原文 / README

mode-registry

One /mode switch that every plugin can offer a mode to. You install the registry once; a model router, an "artifact mode", or the effort modes beside this template each add their own mode to it. You see the active mode as a dim label in the prompt footer (review mode, beside focus and the others) and switch with /mode.

This is the "mods composing" pattern: the registry doesn't know what any mode does. It keeps the list and the current choice. Each plugin reads the choice and changes its own behaviour.

What you can do

| You type | What happens | | --- | --- | | /mode | Opens a picker above the prompt: one button per mode (hotkeys 1-9, 0 for off) and Close. Click it or press ctrl+x tab to give it the keys. | | /mode review | Switches to the mode with id review. The footer reads review mode. | | /mode off | Clears the mode (none and clear work too). | | /mode list | Prints every mode on offer, the plugin that offers it, and which one is on. |

The active mode lives in $.state for the session and isn't saved between sessions (to save it, write it to $.store as well). The registry doesn't reset it on /clear. Whether the engine keeps $.state across a /clear has not been checked.

What it costs

No model calls, ever. The footer label and the picker are UI only and never enter the context. Each /mode command adds one short line to the transcript, which the model reads (about 10-20 tokens; /mode list is about 15 tokens per mode). A press in the picker adds nothing: it switches the mode silently, so the model isn't told (use /mode <id> when you want it to know). That line is appended after the cached prefix, so it never breaks the prompt cache. Whatever a mode costs is up to the plugin that offers it. For example, effort-modes' switch costs one cache rebuild (see its README).

Offering a mode from your plugin

The contract is types/index.d.ts. It has three values this plugin owns:

  • mode-registry.catalog: ModeSpec[], every mode on offer
  • mode-registry.active: string | null, the active mode's id
  • mode-registry.isPicking: boolean, whether the picker is open

Only the registry writes them. You offer a mode by hooking the registry's write to catalog and adding yours:

on('state.set', { plugin: 'mode-registry', key: 'catalog' }, ($, e, next) =>
  next({
    ...e,
    value: [
      ...e.value.filter(m => m.id !== 'router'),
      { id: 'router', label: 'Router', description: 'Picks the model per turn', owner: 'my-router' },
    ],
  }),
)

Then read the active mode wherever your behaviour lives:

const { value: active = null } = await $.state.get({ plugin: 'mode-registry', key: 'active' })
if (active !== 'router') return next(e)

Some details that matter:

  • Filter your own id before adding it. The registry rewrites the catalog at session start and on every /mode. Filtering first keeps your offer from appearing twice.
  • Read active in the hook that acts. Don't copy it into a module variable you keep across turns: a hot reload wipes module variables, and a value read inside a ui.render subscribes you to redraws.
  • If the registry isn't installed, nothing breaks. active reads as undefined at version 0, your state.set hook never fires, and your plugin runs its default behaviour.
  • To switch modes from code (an auto-router that flips to review when it sees a diff), call $.command.run({ command: 'mode', args: 'review' }). It goes through the same path and the same vetoes as the person typing it. It queues until the session is idle.
  • To refuse or redirect a switch, hook state.set on { plugin: 'mode-registry', key: 'active' } and pass next another value. /mode reports what actually landed, so the person sees the veto.

For type-checking, list the registry in your plugin.json: "dependencies": ["mode-registry"]. The engine then writes this contract into your plugin's .claude-plugin/types/mode-registry/ when it loads it from your folder, and your tsconfig.json picks it up (effort-modes does this). The catch: a plugin with that dependency doesn't load at all without the registry. If yours should also work alone, leave the dependency out and run /plugin-types in a session where mode-registry is enabled instead. Never copy the file into your plugin by hand.

Composes with

  • effort-modes (beside this template): offers ui, api and review.
  • Any plugin that draws in the SessionMode footer or the AbovePrompt band. The registry adds its label to e.props.modes and passes the rest on, and draws {await next(e)} under its picker.

Install / load

claude plugin validate templates/mode-registry
claude plugin test templates/mode-registry           # 4 tests, incl. a veto from a third plugin
claude --plugin-dir templates/mode-registry --plugin-dir templates/effort-modes

Function hooks are early access. If your build doesn't load hooks modules by default, set CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1. That setting loads the hooks module of every installed plugin that ships one, not only this one.

更多類似作品