ClaudeMods
☰
JA
● 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 で切り替えます。

registry は各モードの動作を知りません。リストと現在の選択だけを管理します。各プラグインが選択を読み、自分の動作を変えます。

できること

| 入力 | 結果 | | --- | --- | | /mode | プロンプト上に picker を開きます。モードごとのボタン(ホットキー 1-9、0 は off)と Close が表示されます。クリックするか ctrl+x tab を押すとキー入力を picker に渡します。 | | /mode review | id review のモードに切り替えます。フッターは review mode になります。 | | /mode off | モードを消します(none と clear も使えます)。 | | /mode list | 提供されている全モード、提供元プラグイン、現在オンのモードを表示します。 |

アクティブモードはセッションの $.state にあり、セッション間では保存されません(保存するには $.store にも書きます)。registry は /clear でリセットしません。/clear 後も $.state が保持されるかは未確認です。

コスト

モデル呼び出しはありません。 フッターのラベルと picker は UI だけで、コンテキストには入りません。各 /mode コマンドは短い 1 行をトランスクリプトに追加し、モデルはそれを読みます(約 10-20 token、/mode list はモードごとに約 15 token)。picker のキー入力は何も追加せず静かに切り替えるため、モデルには通知されません(知らせたいときは /mode <id> を使います)。この行はキャッシュ済みプレフィックスの後ろに追加されるので prompt cache を壊しません。モード自体のコストは提供元プラグイン次第です。たとえば effort-modes の切り替えはキャッシュ再構築を 1 回発生させます。

プラグインからモードを提供する

契約は types/index.d.ts にあり、このプラグインが所有する 3 つの値を定義します。

  • mode-registry.catalog:ModeSpec[]、提供中の全モード
  • mode-registry.active:string | null、現在のモードの id
  • mode-registry.isPicking:boolean、picker が開いているか

これらを書き込めるのは registry だけです。catalog への registry の書き込みを hook してモードを追加します。

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 を filter します。registry はセッション開始時と /mode のたびに catalog を書き換えるため、先に filter すれば二重登録を防げます。
  • active は動作する hook で読みます。ターンをまたぐモジュール変数にコピーしないでください。ホットリロードで変数が消え、ui.render 内で読むと再描画を購読することになります。
  • registry が未インストールでも壊れません。version 0 では active は undefined になり、state.set hook は呼ばれず、プラグインは既定の動作をします。
  • コードから切り替える場合(差分を見た自動ルーターが review に切り替える場合など)は $.command.run({ command: 'mode', args: 'review' }) を呼びます。同じ経路と veto を通り、セッションが idle になるまでキューに入ります。
  • 切り替えを拒否またはリダイレクトするには、{ 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 に追加して残りを通し、picker の下に {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.

関連作品