Agent Toggle
<instructions>Disable/enable individual plugin agents by renaming
<name>.md↔_<name>.mdin the plugin cache. State persisted globally (default) or per-project. See also:/brewtools:skill-togglefor skills.
Robustness Rules
| Rule | Applies |
|---|---|
Every Bash call ends with && echo "OK ..." || echo "FAILED ..." | ALL |
Never use Write/Edit on ~/.claude/plugins/* — use Bash + Node fs | ALL |
This skill mutates agents only (kind='agent') — never skills | ALL |
Kind is hardcoded — do NOT call detectKind; skill IS the kind | P1 |
Paths (substitute literally in Bash):
- Shared helpers:
$CLAUDE_PLUGIN_ROOT/skills/_shared/toggle/ - Global state:
$CLAUDE_PLUGIN_DATA/toggle-state.json(fallback:~/.claude/plugins/data/brewtools-claude-brewcode/toggle-state.json) - Project state:
$PWD/.claude/brewtools/toggle-state.json
Phase I — Interactive Flow (entry gate)
Full spec:
_shared/toggle/interactive-flow.md(phases I0-I4). Read it fully on entry. This section hardcodeskind='agent'— nodetectKindcall.
Enter interactive flow when:
- No args given, OR
- User prompt is freeform without a concrete
plugin:nametarget (e.g. "отключи лишнего агента", "hide the noisy one"), OR - Parsed target missing from cache.
Skip interactive (go straight to P0 → P1 → P2 → P3) when op AND target are both explicit: /brewtools:agent-toggle disable brewtools:ssh-admin.
| Phase | Action | Tool |
|---|---|---|
| I0 | Decide branch from input shape | — |
| I1 | Op picker — single AskUserQuestion, 4 options (status, disable, enable, list), pre-selected hint: disable | AskUserQuestion |
| I2 | Catalog one-liner — Bash+Node imports enumeratePlugins from $CLAUDE_PLUGIN_ROOT/skills/_shared/toggle/cache.mjs, emit AVAILABLE TO {OP} (N total, Ctrl+F to search): header then single space-separated line of plugin:name tokens (filter: disable→enabled only, enable→disabled only, kind=agent). Free-text "Which one?" prompt | Bash |
| I3 | Resolve + confirm once: exact plugin:name or unique name→no confirm; fuzzy→one AskUserQuestion Disable X? [yes / pick different / cancel]; multiple→AskUserQuestion 2-4 options | AskUserQuestion |
| I4 | Execute (P1→P2→P3) then ALWAYS print current state (see format below) | Bash |
Terminal ops (list, reapply, prune, status) skip to I4 directly — no picker, no catalog.
I4 status format (always printed):
DISABLED RIGHT NOW
-------------------
brewtools:ssh-admin [agent, global, since 2026-04-16]
(none) ← if empty
ENABLED (M agents across P plugins)
P0: Parse Intent
Parse $ARGUMENTS (or the user's NL prompt) into structured form:
{ op: disable|enable|status|list|reapply|prune, scope: global|project, targets: [{plugin, name}] }
Rules:
- Default scope =
global. Override with--scope=project(or phrase "for this project"). - Target format:
plugin:name(e.g.,brewtools:ssh-admin). If onlynamegiven → ask AskUserQuestion which plugin. status,list,reapply,prunetake no targets.- Multiple targets allowed for
disable/enable— iterate P2–P3 per target.
If ambiguous (no plugin prefix for disable/enable) → AskUserQuestion with candidate plugins from P1 enumerate.
P1: Validate Target (per target)
EXECUTE using Bash tool:
node --input-type=module -e "
import {enumeratePlugins, resolveTarget} from '$CLAUDE_PLUGIN_ROOT/skills/_shared/toggle/cache.mjs';
import fs from 'node:fs';
const p = enumeratePlugins().get('PLUGIN_NAME');
if (!p) { console.log(JSON.stringify({error:'plugin_not_installed', plugin:'PLUGIN_NAME'})); process.exit(0); }
const t = resolveTarget(p, 'agent', 'AGENT_NAME');
const visibleExists = fs.existsSync(t.visible);
const hiddenExists = fs.existsSync(t.hidden);
if (!visibleExists && !hiddenExists) { console.log(JSON.stringify({error:'agent_not_found', plugin:p.plugin, name:'AGENT_NAME'})); process.exit(0); }
console.log(JSON.stringify({plugin:p.plugin, latest:p.latest, visible:t.visible, hidden:t.hidden, visibleExists, hiddenExists}));
" && echo "OK validate" || echo "FAILED validate"
Replace PLUGIN_NAME, AGENT_NAME. On error → stop, report to user.
P2: Mutate State
EXECUTE using Bash tool (disable example):
node --input-type=module -e "
import {readState, writeStateAtomic, stateKey, globalStatePath, projectStatePath} from '$CLAUDE_PLUGIN_ROOT/skills/_shared/toggle/state.mjs';
const fp = 'SCOPE'==='project' ? projectStatePath(process.cwd()) : globalStatePath();
const st = readState(fp);
const key = stateKey('PLUGIN','NAME');
st.disabled[key] = {kind:'agent', plugin:'PLUGIN', name:'NAME', disabled_at:new Date().toISOString(), last_applied_version:'VERSION'};
writeStateAtomic(fp, st);
console.log(JSON.stringify({scope:'SCOPE', file:fp, key}));
" && echo "OK state" || echo "FAILED state"
For enable — replace body with delete st.disabled[key].
P3: Apply Rename
EXECUTE using Bash tool (disable):
node --input-type=module -e "
import {disableTarget} from '$CLAUDE_PLUGIN_ROOT/skills/_shared/toggle/apply.mjs';
const r = disableTarget('VISIBLE_PATH','HIDDEN_PATH');
console.log(JSON.stringify(r));
" && echo "OK apply" || echo "FAILED apply"
For enable — enableTarget(visible, hidden). Substitute absolute paths from P1 output.
Status meanings: disabled, enabled, already_disabled, already_enabled, missing.
P4: Notify
Render a result table and remind user to reload:
# Agent Toggle — <op>
| Plugin | Name | Kind | Scope | Action | Result |
|--------|------|------|-------|--------|--------|
| brewtools | ssh-admin | agent | global | disable | disabled |
> Run `/reload-plugins` (or restart session) for the change to take effect.
Sub-operations
status — merged view
EXECUTE using Bash tool:
node --input-type=module -e "
import {readState, mergeStates, globalStatePath, projectStatePath} from '$CLAUDE_PLUGIN_ROOT/skills/_shared/toggle/state.mjs';
const g = readState(globalStatePath());
const p = readState(projectStatePath(process.cwd()));
const m = mergeStates(g, p);
const rows = Object.entries(m.disabled).filter(([,v])=>v.kind==='agent').map(([k,v])=>({
key:k, ...v,
scope: p.disabled[k] ? 'project' : 'global'
}));
console.log(JSON.stringify({rows}));
" && echo "OK status" || echo "FAILED status"
Render as table: plugin | name | scope | disabled_at | last_applied_version.
list — enumerate all agents
EXECUTE using Bash tool:
node --input-type=module -e "
import {enumeratePlugins, resolveTarget} from '$CLAUDE_PLUGIN_ROOT/skills/_shared/toggle/cache.mjs';
import fs from 'node:fs';
import path from 'node:path';
const out = [];
for (const [plugin, e] of enumeratePlugins()) {
const dir = path.join(e.path, 'agents');
let entries = [];
try { entries = fs.readdirSync(dir, {withFileTypes:true}).filter(d=>d.isFile() && d.name.endsWith('.md') && !d.name.startsWith('_')); } catch {}
for (const d of entries) {
const name = d.name.replace(/\.md$/, '');
const t = resolveTarget(e, 'agent', name);
out.push({plugin, name, version:e.latest, disabled: fs.existsSync(t.hidden) && !fs.existsSync(t.visible)});
}
}
console.log(JSON.stringify(out));
" && echo "OK list" || echo "FAILED list"
Render grouped by plugin; mark disabled rows.
reapply — re-run from state
Read merged state (as in status), filter kind==='agent', for each entry call P1 + P3 (no P2 mutate). Report per-target results.
prune — drop stale entries
For each state entry where plugin no longer in enumeratePlugins() → remove via writeStateAtomic. Report removed keys.