plugsdk
v1.0.20
Published
Host-side SDK — load Claude Code plugins inside your own application
Maintainers
Readme
plugsdk
Embed Claude Code's plugin system in your own application.
npm install plugsdkWhat it is
plugsdk is the host-side loader for Claude Code plugins. Drop it into any Node application and you can:
- Load plugins authored against Claude Code's plugin format from disk.
- Read every documented component — manifest, hooks, skills, commands, agents, MCP server configs, LSP server configs, monitors, themes, output styles, settings, marketplace catalogs.
- Dispatch your application's events through the plugins' hooks the same way Claude Code does, with subprocess execution, JSON I/O, matcher filtering, and result merging following the documented precedence rules.
plugsdk does not author plugins. It does not ship UI. It loads on-disk plugin directories and gives you their structure plus a hook dispatcher.
Reference: https://code.claude.com/docs/en/plugins-reference and https://code.claude.com/docs/en/hooks.
Quick start
import { loadClaudePlugin, createHookDispatcher } from 'plugsdk'
const plugin = loadClaudePlugin('/path/to/some-plugin')
const dispatch = createHookDispatcher([plugin])
// Your app is about to run a tool. Ask plugins first:
const result = await dispatch('PreToolUse', {
tool_name: 'Bash',
tool_input: { command: 'rm -rf /' },
cwd: process.cwd(),
})
if (result.hookSpecificOutput?.permissionDecision === 'deny') {
throw new Error(result.hookSpecificOutput.permissionDecisionReason)
}What loadClaudePlugin(dir) returns
{
root, // absolute plugin dir
manifest, // .claude-plugin/plugin.json
hooks, // { PreToolUse: [{ matcher, hooks: [...] }], ... }
skills, // [{ name, dir, file, fields, body }]
commands, // flat skills/commands
agents, // [{ name, file, fields, body }] — validated
mcpServers, // .mcp.json mcpServers map
lspServers, // .lsp.json
monitors, // monitors/monitors.json array
themes, // [{ slug, name, base, overrides }]
outputStyles, // output-styles/*.md
settings, // settings.json
userConfig, // manifest.userConfig
channels, // manifest.channels
dependencies, // manifest.dependencies
binDir, // bin/ if present
}The manifest's custom-path fields (skills, commands, agents,
outputStyles, themes, monitors, hooks, mcpServers, lspServers)
are honored — when a manifest specifies a custom path, the default
location is skipped, exactly as the Claude Code spec mandates.
Forbidden agent fields (hooks, mcpServers, permissionMode) and
illegal isolation values cause the loader to throw at parse time so
malformed plugins are rejected up front.
Loading marketplaces
import { loadClaudeMarketplace } from 'plugsdk'
const m = loadClaudeMarketplace('/path/to/some-marketplace')
// m.catalog: marketplace.json
// m.plugins: [{ entry, plugin: LoadedClaudePlugin | null }]
// local-path sources auto-load; remote sources kept as metadata.metadata.pluginRoot is honored when resolving local plugin paths.
Dispatching hooks
const dispatch = createHookDispatcher(plugins, { timeout: 60_000 })
const r = await dispatch('PreToolUse', payload)For each loaded plugin whose hooks[event] entries match the payload's
target (tool name / source / agent type / etc.), every command-type
handler runs as a subprocess:
- The command string has
${CLAUDE_PLUGIN_ROOT},${CLAUDE_PLUGIN_DATA},${user_config.<key>}, and${ENV_VAR}substituted. - The full payload (with
hook_event_nameinjected) is written to stdin as JSON. CLAUDE_PLUGIN_ROOTandCLAUDE_PROJECT_DIRare exported on the child's env.- stdout JSON is parsed; stderr is captured for exit-code-2 blocks.
Returned shape:
{
results: [{ plugin, exitCode, stdout, stderr, output, eventName, handler }, ...],
unhandled: [...], // non-command handlers (http/mcp_tool/prompt/agent)
decision?: 'block', reason?: string, // top-level decision events
hookSpecificOutput?: { // permission events
hookEventName,
permissionDecision: 'allow' | 'deny' | 'ask' | 'defer',
permissionDecisionReason,
additionalContext, // joined across plugins
updatedInput, // shallow-merged
},
continue?, stopReason?, suppressOutput?, systemMessage?,
}Result merging follows the documented precedence:
- PreToolUse / PermissionRequest:
deny > defer > ask > allow. - Stop / UserPromptSubmit / PostToolBatch / etc.: first plugin emitting
decision: 'block'(or exit code 2) wins. additionalContextentries are concatenated.continue: false/suppressOutput/systemMessagepropagate.
Non-command handler types are returned in unhandled so your host can
route them itself (HTTP webhook, an in-process MCP server call, etc).
Matcher rules
claudeAdapter.matches(matcher, target) implements Claude Code's
documented matcher semantics:
| matcher | meaning |
|---|---|
| omitted / '' / '*' | match every target |
| Bash | exact match |
| Bash\|Edit | pipe-separated literal list |
| anything else | JavaScript regex |
License
MIT © AnEntrypoint
