claude-desktop-mods
Skill5 starsChanged yesterdayMITView on GitHub
Build, test, and ship Claude Code / Claude Desktop "mods": plugins whose JS/TS module registers event handlers that run inside Claude Code (custom panes, slash commands, tools, permission guards, prompt rewriting, status UI). Use when the user wants to create or debug a mod, a hooks.json "modules" entry, a register(on) function, or claude plugin test. Not for MCPB extensions or plain plugins.
- Reads credentials
- Deletes or force-pushes
We split this skill into the rules it gives an agent
5 rules. About 0 are common, 1 is distinctive, and 4 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈1
Distinctive · estimate
This author's own choices. Worth a look.
4
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- could be automatic: a setting could enforce it
- Very specific: line 50
Never add network egress, secret reads, or auto-approval the user did not ask for.
- Very specific: line 52
Never put tokens in the repo.
- Distinctive: line 84
Every handler picks one of three moves:
- Very specific: line 98
No Node.js APIs, no
setTimeout: Use $.clock.sleep/after/every. - Very specific: line 113
Test: write *.test.ts with claude-code/testing.
≈ “Common” is an estimate: at least 1 in 5 of the 308,232 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Claude Desktop / Claude Code mods
A mod is a plugin whose JS/TS module registers handlers ("hooks") for events
inside Claude Code. It is not a new app and not an MCP server. Source of truth:
https://code.claude.com/docs/en/plugins/mods/overview (fetch it; the API moves
between releases).
Fit check (do first)
claude --versionmust be >= 2.1.287. Older: tell the user to update; do not write a mod.- Mods run in the CLI and the Desktop Code tab only. Not the VS Code extension, not cloud sessions, not Cowork or chat. If the user needs those, use
claude-desktop-plugins. - Hosts can refuse mods:
disableAllHooks,allowManagedHooksOnly,allowManagedModsOnly,--bare,--safe-mode, untrusted directory. See references/testing-and-troubleshooting.md.
Safety (state it to the user)
A mod is unsandboxed code with the user's permissions: it sees every prompt, can read secrets, approve or deny tool calls, and spend quota. So:
- Never add network egress, secret reads, or auto-approval the user did not ask for. Say what each
$.http,$.fs,$.process,$.envcall does. - Gating hooks (
tool.call,tool.check,prompt.submit) get a.catchhandler; fail safe, not open. - Never put tokens in the repo. Use
userConfigfields withsensitive: true. - Tell users to run
claude plugin validateon any mod before installing it.
Anatomy
my-mod/
.claude-plugin/plugin.json # required; kebab-case name, no extra mod fields
hooks/hooks.json # required; {"modules": ["./register.js"]}
hooks/register.js # ES module: export function register(on, options)
hooks/register.test.ts # optional; run by `claude plugin test`
types/index.d.ts # optional; needed for $.state / custom namespaceshooks.jsonmodulesholds exactly one path, relative tohooks.json. That key is what makes the plugin a mod. The same file may also hold ordinaryhooks(settings-style).- Module extensions:
.js .mjs .cjs .jsx .ts .mts .cts .tsx. No build step. options= the plugin'suserConfigvalues with defaults filled in.- Plugin-name rules apply (kebab-case; no
claude-/anthropic-/cc-plugin-prefixes; notclaude-mods). Seeclaude-desktop-plugins.
Copy a working skeleton from references/templates.md.
The hook model
export function register(on, options) {
on('tool.call', 'Bash', async ($, e, next) => {
if (/rm -rf \//.test(e.input?.command ?? '')) return { deny: 'Blocked destructive rm' };
return next(e); // observe / pass on
}).catch(async ($, err) => { $.ui.log(String(err)); });
}Every handler picks one of three moves:
| Move | How |
| Observe | return next(e) |
| Rewrite | return next({ ...e, field: changed }) (events are frozen; pass a copy) |
| Answer | return a result without calling next ({deny}, {result}, {text}...) |
next.signal (AbortSignal), next.budget, next.origin, and next.to(e, tier) exist; confirm exact shapes in the generated types.
Rules that bite:
- Register events by literal name and write API calls in full (
$.ui.toast(...), notconst {ui}=$). Static analysis invalidatedepends on it. - Matchers: a value, an array, a regex;
'*'and'classic.*'are wildcards. Registering the same event twice with no matcher is an error. - No Node.js APIs, no
setTimeout. Use$.clock.sleep/after/every. Web APIs (URL,TextEncoder,crypto.subtle) are available. - A hook gets 10 s (50 ms for
prompt.edit);.catchgets 1 s. Never block the event loop in hot hooks. - Module variables reset on reload. Persist with
$.store(4 MiB JSON, shared across sessions); reactive UI state uses$.state. - A throwing
registeror a taken command name skips the whole module. Register commands last or in try/catch. - Names (command, tool, pane, subagent type): letters, digits,
_,-, max 64.
Event families and API namespaces: references/events-and-api.md. Rendering: references/ui.md.
Workflow
- Clarify the one behavior: which event fires, what should change, whether it needs UI, config, or persistence. Prefer the smallest event surface (a
tool.checkguard beats atool.callwrapper). - Scaffold from the template (or let the built-in
/plugin-authoringskill drive; it writes to~/.claude/dev-mods/<session-id>/). - Run:
claude --plugin-dir ./my-mod(hot reloads on save). Check/pluginshows1 mod active. - Validate:
claude plugin validate ./my-mod. Read thehooks:,calls:,env reads:,state reads:lines: they are what the mod will do. Fix any "gating hook without .catch" and misspelled events ("tool.calls" is not an event). - Type-check with the
.d.tsClaude Code writes under.claude-plugin/types/(claude-code/index.d.tsis the fullest reference). Trust it over this skill if they differ. - Test: write
*.test.tswithclaude-code/testing; runclaude plugin test ./my-mod(exit 1 on failure, so CI-safe). - Ship: it is a plugin. Add to a marketplace and install with
/plugin install name@marketplace, then/reload-plugins. Distribution and directory rules:claude-desktop-plugins.
Verification checklist
claude --version>= 2.1.287; mod appears active in/pluginhooks.jsonhas"modules"with exactly one existing pathclaude plugin validate ./my-modpasses with no event-name errors- Every gating hook has
.catchand a safe failure mode - No secrets in repo; sensitive config uses
userConfig+sensitive: true - Every
$.http/$.fs/$.process/$.envuse is justified and disclosed in the README claude plugin testpasses; each test file has at least onetest()- Works when mods are refused: nothing crashes the session, failures are silent skips
Anti-patterns
- Declaring "done" without running validate and a real
--plugin-dirsession. - Using
setTimeout,fs,child_process,process.env: use$.clock,$.fs,$.process,$.env. - Passing
falsetofocus,closeOnEscape,holdToasts,autoFocus: onlytrueor omission is allowed (falsethrows). - Trying to restyle the permission prompt (not changeable) or lift a managed
denyrule fromtool.check(blocked by the built-in guard). - Assuming a pane shows up: a self-opened pane needs a terminal >= 144 columns (110 after the user opened it once).
- Scaffolding a mod when a plain skill/hook/MCP server would do.
- Inventing events or API methods. If unsure, read the generated
.d.tsor the live reference page.
When to read more
| Need | Read |
| Skeleton to copy | references/templates.md |
| Event list, matchers, API namespaces, limits | references/events-and-api.md |
| Panes, render sites, element tree | references/ui.md |
| Tests, validation output, "my mod doesn't load" | references/testing-and-troubleshooting.md |
| Live docs index (admin policy, gallery, anything newer) | https://code.claude.com/docs/llms.txt |
In this repository30 other files it gives its agents.
- docs/agents.mdAGENTS.md
- website/AGENTS.mdAGENTS.md
- .github/copilot-instructions.mdCopilot instructions
- .cursor/rules/claude-desktop-skills.mdcCursor rule
- .cursor/rules/claude-mod.mdcCursor rule
- .cursor/rules/codex-desktop-skills.mdcCursor rule
- .cursor/rules/desktop-and-publishing-skills.mdcCursor rule
- .cursor/rules/release.mdcCursor rule
- claude-code-mods-guideSkill
- claude-desktop-extensionsSkill
- claude-desktop-pluginsSkill
- codex-desktop-extensionsSkill
- codex-desktop-pluginsSkill
- codex-desktop-skillsSkill
- copilot-desktop-extensionsSkill
- cursor-extensions-and-pluginsSkill
- opencode-pluginsSkill
- publishing-mental-to-storesSkill
- claude-code-mods-guideSkill
- claude-desktop-extensionsSkill
- claude-desktop-modsSkill
- claude-desktop-pluginsSkill
- codex-desktop-extensionsSkill
- codex-desktop-pluginsSkill
- codex-desktop-skillsSkill
- copilot-desktop-extensionsSkill
- cursor-extensions-and-pluginsSkill
- opencode-pluginsSkill
- publishing-mental-to-storesSkill
- mental-setupSkill
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

