agentleFS
afaraha8403afaraha8403/mental/.agents/skills/claude-desktop-mods/SKILL.md

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
  1. Very specific:

    Never add network egress, secret reads, or auto-approval the user did not ask for.

    line 50
  2. Very specific:

    Never put tokens in the repo.

    line 52
  3. Distinctive:

    Every handler picks one of three moves:

    line 84
  4. Very specific:

    No Node.js APIs, no setTimeout: Use $.clock.sleep/after/every.

    line 98
  5. Very specific:

    Test: write *.test.ts with claude-code/testing.

    line 113

≈ “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.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

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)

  1. claude --version must be >= 2.1.287. Older: tell the user to update; do not write a mod.
  2. 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.
  3. 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, $.env call does.
  • Gating hooks (tool.call, tool.check, prompt.submit) get a .catch handler; fail safe, not open.
  • Never put tokens in the repo. Use userConfig fields with sensitive: true.
  • Tell users to run claude plugin validate on 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 namespaces
  • hooks.json modules holds exactly one path, relative to hooks.json. That key is what makes the plugin a mod. The same file may also hold ordinary hooks (settings-style).
  • Module extensions: .js .mjs .cjs .jsx .ts .mts .cts .tsx. No build step.
  • options = the plugin's userConfig values with defaults filled in.
  • Plugin-name rules apply (kebab-case; no claude-/anthropic-/cc-plugin- prefixes; not claude-mods). See claude-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:

MoveHow
Observereturn next(e)
Rewritereturn next({ ...e, field: changed }) (events are frozen; pass a copy)
Answerreturn 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(...), not const {ui}=$). Static analysis in validate depends 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); .catch gets 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 register or 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

  1. Clarify the one behavior: which event fires, what should change, whether it needs UI, config, or persistence. Prefer the smallest event surface (a tool.check guard beats a tool.call wrapper).
  2. Scaffold from the template (or let the built-in /plugin-authoring skill drive; it writes to ~/.claude/dev-mods/<session-id>/).
  3. Run: claude --plugin-dir ./my-mod (hot reloads on save). Check /plugin shows 1 mod active.
  4. Validate: claude plugin validate ./my-mod. Read the hooks:, 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).
  5. Type-check with the .d.ts Claude Code writes under .claude-plugin/types/ (claude-code/index.d.ts is the fullest reference). Trust it over this skill if they differ.
  6. Test: write *.test.ts with claude-code/testing; run claude plugin test ./my-mod (exit 1 on failure, so CI-safe).
  7. 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 /plugin
  • hooks.json has "modules" with exactly one existing path
  • claude plugin validate ./my-mod passes with no event-name errors
  • Every gating hook has .catch and a safe failure mode
  • No secrets in repo; sensitive config uses userConfig + sensitive: true
  • Every $.http/$.fs/$.process/$.env use is justified and disclosed in the README
  • claude plugin test passes; each test file has at least one test()
  • Works when mods are refused: nothing crashes the session, failures are silent skips

Anti-patterns

  • Declaring "done" without running validate and a real --plugin-dir session.
  • Using setTimeout, fs, child_process, process.env: use $.clock, $.fs, $.process, $.env.
  • Passing false to focus, closeOnEscape, holdToasts, autoFocus: only true or omission is allowed (false throws).
  • Trying to restyle the permission prompt (not changeable) or lift a managed deny rule from tool.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.ts or the live reference page.

When to read more

NeedRead
Skeleton to copyreferences/templates.md
Event list, matchers, API namespaces, limitsreferences/events-and-api.md
Panes, render sites, element treereferences/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

Download now (ZIP)

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
What did you use it for? e.g. Reviewing pull requests in a Rails app

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.