ClaudeMods
☰
KO
● 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 명령은 짧은 한 줄을 transcript에 추가하고 모델이 읽습니다(약 10-20 token; /mode list는 모드당 약 15 token). picker에서 누르는 키는 아무것도 추가하지 않고 조용히 전환하므로 모델에 알리지 않습니다(알려야 하면 /mode <id> 사용). 이 줄은 캐시된 접두사 뒤에 추가되어 prompt cache를 깨지 않습니다. 모드 자체의 비용은 제공 플러그인에 달렸습니다. 예를 들어 effort-modes 전환은 캐시를 1번 재구축합니다.

플러그인에서 모드 제공

계약은 types/index.d.ts에 있으며 이 플러그인이 소유한 세 값을 정의합니다.

  • 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하면 중복 등록을 막습니다.
  • 실제로 동작하는 hook에서 active를 읽습니다. 턴 사이에 유지되는 모듈 변수에 복사하지 마세요. 핫 리로드가 변수를 지우고 ui.render 안에서 읽으면 다시 그리기를 구독하게 됩니다.
  • registry가 설치되지 않아도 문제가 없습니다. version 0에서 active는 undefined로 읽히고 state.set hook은 실행되지 않으며 플러그인은 기본 동작을 합니다.
  • 코드에서 전환하려면(예: 자동 라우터가 diff를 보고 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.

비슷한 프로젝트