@agentplugins/adapter-claude 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -0
- package/dist/index.cjs +509 -0
- package/dist/index.d.cts +120 -0
- package/dist/index.d.ts +120 -0
- package/dist/index.js +488 -0
- package/package.json +36 -0
package/README.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# @agentplugins/adapter-claude
|
|
2
|
+
|
|
3
|
+
> AgentPlugins platform adapter for [Claude Code](https://code.claude.com/docs/en/plugins-reference).
|
|
4
|
+
|
|
5
|
+
Compiles a universal `PluginManifest` into Claude Code's native plugin layout: `plugin.json` manifest, `hooks.json` hook wiring, inline handler scripts, `skills/`, and MCP server configuration.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @agentplugins/adapter-claude
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
This package is typically installed transitively via [`@agentplugins/cli`](https://www.npmjs.com/package/@agentplugins/cli). Install it directly only if you're building a custom build pipeline.
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { createClaudeAdapter } from '@agentplugins/adapter-claude';
|
|
19
|
+
|
|
20
|
+
const adapter = createClaudeAdapter();
|
|
21
|
+
const output = await adapter.compile(manifest);
|
|
22
|
+
// → output.files: FileOutput[] of plugin.json, hooks/handlers, skills/, MCP config
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
You usually don't call the adapter directly — let `@agentplugins/cli` route to it via the registry:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npx agentplugins build --target claude
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Output shape
|
|
32
|
+
|
|
33
|
+
A successful build writes to `dist/claude/`:
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
dist/claude/
|
|
37
|
+
├── .claude-plugin/
|
|
38
|
+
│ └── plugin.json
|
|
39
|
+
├── hooks/
|
|
40
|
+
│ └── hooks.json
|
|
41
|
+
├── skills/
|
|
42
|
+
│ └── <skill-name>/
|
|
43
|
+
│ └── SKILL.md
|
|
44
|
+
└── .mcp.json
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Install with: `cp -r dist/claude ~/.claude/skills/<plugin-name>`
|
|
48
|
+
|
|
49
|
+
## Supported hooks
|
|
50
|
+
|
|
51
|
+
`PreToolUse`, `PostToolUse`, `UserPromptSubmit`, `Stop`, `SubagentStop`, `SessionStart`, `SessionEnd`. See [HookContext in `@agentplugins/core`](https://www.npmjs.com/package/@agentplugins/core) for the universal hook names.
|
|
52
|
+
|
|
53
|
+
## License
|
|
54
|
+
|
|
55
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
createAdapter: () => createAdapter,
|
|
24
|
+
createClaudeAdapter: () => createClaudeAdapter,
|
|
25
|
+
default: () => createClaudeAdapter
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(index_exports);
|
|
28
|
+
var import_core = require("@agentplugins/core");
|
|
29
|
+
var CLAUDE_SUPPORTED_HOOKS = [
|
|
30
|
+
"sessionStart",
|
|
31
|
+
"sessionEnd",
|
|
32
|
+
"userPromptSubmit",
|
|
33
|
+
"userPromptExpansion",
|
|
34
|
+
"preToolUse",
|
|
35
|
+
"postToolUse",
|
|
36
|
+
"postToolUseFailure",
|
|
37
|
+
"permissionRequest",
|
|
38
|
+
"permissionDenied",
|
|
39
|
+
"subagentStart",
|
|
40
|
+
"subagentStop",
|
|
41
|
+
"preCompact",
|
|
42
|
+
"postCompact",
|
|
43
|
+
"stop",
|
|
44
|
+
"stopFailure",
|
|
45
|
+
"notification",
|
|
46
|
+
"fileChanged",
|
|
47
|
+
"cwdChanged",
|
|
48
|
+
"setup"
|
|
49
|
+
];
|
|
50
|
+
var CLAUDE_SUPPORTED_HANDLERS = [
|
|
51
|
+
"command",
|
|
52
|
+
"http",
|
|
53
|
+
"inline"
|
|
54
|
+
// auto-wrapped as command scripts
|
|
55
|
+
];
|
|
56
|
+
var HOOK_NAME_MAP = {
|
|
57
|
+
sessionStart: "SessionStart",
|
|
58
|
+
sessionEnd: "SessionEnd",
|
|
59
|
+
userPromptSubmit: "UserPromptSubmit",
|
|
60
|
+
userPromptExpansion: "UserPromptExpansion",
|
|
61
|
+
preToolUse: "PreToolUse",
|
|
62
|
+
postToolUse: "PostToolUse",
|
|
63
|
+
postToolUseFailure: "PostToolUseFailure",
|
|
64
|
+
permissionRequest: "PermissionRequest",
|
|
65
|
+
permissionDenied: "PermissionDenied",
|
|
66
|
+
subagentStart: "SubagentStart",
|
|
67
|
+
subagentStop: "SubagentStop",
|
|
68
|
+
preCompact: "PreCompact",
|
|
69
|
+
postCompact: "PostCompact",
|
|
70
|
+
stop: "Stop",
|
|
71
|
+
stopFailure: "StopFailure",
|
|
72
|
+
notification: "Notification",
|
|
73
|
+
fileChanged: "FileChanged",
|
|
74
|
+
cwdChanged: "CwdChanged",
|
|
75
|
+
setup: "Setup"
|
|
76
|
+
};
|
|
77
|
+
function createClaudeAdapter() {
|
|
78
|
+
return new ClaudePlatformAdapter();
|
|
79
|
+
}
|
|
80
|
+
function createAdapter() {
|
|
81
|
+
return createClaudeAdapter();
|
|
82
|
+
}
|
|
83
|
+
var ClaudePlatformAdapter = class {
|
|
84
|
+
name = "claude";
|
|
85
|
+
displayName = "Claude Code";
|
|
86
|
+
supportedHooks = CLAUDE_SUPPORTED_HOOKS;
|
|
87
|
+
supportedHandlers = CLAUDE_SUPPORTED_HANDLERS;
|
|
88
|
+
manifestPath = ".claude-plugin/plugin.json";
|
|
89
|
+
manifestFormat = "json";
|
|
90
|
+
// Inline handlers are wrapped as command scripts; tracks wrapper ID -> hook name
|
|
91
|
+
inlineWrappers = /* @__PURE__ */ new Map();
|
|
92
|
+
// ─── Validation ────────────────────────────────────────────────────────────
|
|
93
|
+
validate(plugin) {
|
|
94
|
+
const issues = [...(0, import_core.validateForPlatform)(plugin, "claude")];
|
|
95
|
+
if (plugin.hooks) {
|
|
96
|
+
for (const [name, def] of Object.entries(plugin.hooks)) {
|
|
97
|
+
const hookName = name;
|
|
98
|
+
if (!def) continue;
|
|
99
|
+
if (def.handler.type === "inline") {
|
|
100
|
+
const handler = def.handler;
|
|
101
|
+
if (typeof handler.handler !== "function") {
|
|
102
|
+
issues.push({
|
|
103
|
+
severity: import_core.Severity.ERROR,
|
|
104
|
+
field: `hooks.${hookName}.handler`,
|
|
105
|
+
message: "Inline handler must be a function"
|
|
106
|
+
});
|
|
107
|
+
} else {
|
|
108
|
+
issues.push({
|
|
109
|
+
severity: import_core.Severity.INFO,
|
|
110
|
+
field: `hooks.${hookName}.handler`,
|
|
111
|
+
message: "Inline handler will be auto-wrapped as a Claude command script",
|
|
112
|
+
suggestion: 'For better performance, use a "command" or "http" handler'
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (def.matcher && ["preToolUse", "postToolUse", "postToolUseFailure"].includes(hookName)) {
|
|
117
|
+
if (def.matcher.includes("*") && !def.matcher.match(/^\*?\w+\*?$/)) {
|
|
118
|
+
issues.push({
|
|
119
|
+
severity: import_core.Severity.WARNING,
|
|
120
|
+
field: `hooks.${hookName}.matcher`,
|
|
121
|
+
message: `Matcher pattern "${def.matcher}" may not be supported by Claude for tool hooks`,
|
|
122
|
+
suggestion: 'Use simple patterns like "toolName", "prefix*", or "*suffix"'
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (plugin.mcpServers) {
|
|
129
|
+
for (const [serverName, config] of Object.entries(plugin.mcpServers)) {
|
|
130
|
+
if (!config.command) {
|
|
131
|
+
issues.push({
|
|
132
|
+
severity: import_core.Severity.ERROR,
|
|
133
|
+
field: `mcpServers.${serverName}.command`,
|
|
134
|
+
message: `MCP server "${serverName}" requires a command`
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
if (config.transport && config.transport !== "stdio" && config.transport !== "http") {
|
|
138
|
+
issues.push({
|
|
139
|
+
severity: import_core.Severity.WARNING,
|
|
140
|
+
field: `mcpServers.${serverName}.transport`,
|
|
141
|
+
message: `Transport "${config.transport}" may not be supported by Claude MCP`,
|
|
142
|
+
suggestion: 'Use "stdio" (default) or "http"'
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (plugin.skills) {
|
|
148
|
+
for (let i = 0; i < plugin.skills.length; i++) {
|
|
149
|
+
const skill = plugin.skills[i];
|
|
150
|
+
if (skill.content && skill.filePath) {
|
|
151
|
+
issues.push({
|
|
152
|
+
severity: import_core.Severity.WARNING,
|
|
153
|
+
field: `skills[${i}]`,
|
|
154
|
+
message: `Skill "${skill.name}" has both content and filePath \u2014 content takes precedence`
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (plugin.userConfig) {
|
|
160
|
+
const supportedTypes = ["string", "number", "boolean", "directory", "file"];
|
|
161
|
+
for (const [key, opt] of Object.entries(plugin.userConfig)) {
|
|
162
|
+
if (!supportedTypes.includes(opt.type)) {
|
|
163
|
+
issues.push({
|
|
164
|
+
severity: import_core.Severity.WARNING,
|
|
165
|
+
field: `userConfig.${key}.type`,
|
|
166
|
+
message: `User config type "${opt.type}" may not be fully supported by Claude`,
|
|
167
|
+
suggestion: `Supported types: ${supportedTypes.join(", ")}`
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return issues;
|
|
173
|
+
}
|
|
174
|
+
// ─── Compilation ───────────────────────────────────────────────────────────
|
|
175
|
+
compile(plugin) {
|
|
176
|
+
this.inlineWrappers.clear();
|
|
177
|
+
const files = [];
|
|
178
|
+
const warnings = [];
|
|
179
|
+
const postInstall = [];
|
|
180
|
+
const claudeManifest = this.buildManifest(plugin);
|
|
181
|
+
const hooksConfig = this.buildHooksConfig(plugin);
|
|
182
|
+
if (hooksConfig.length > 0) {
|
|
183
|
+
files.push({
|
|
184
|
+
path: "hooks/hooks.json",
|
|
185
|
+
content: JSON.stringify(hooksConfig, null, 2)
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
const inlineHandlers = this.extractInlineHandlers(plugin);
|
|
189
|
+
if (inlineHandlers.size > 0) {
|
|
190
|
+
const handlersModule = (0, import_core.generateHandlersModule)(inlineHandlers);
|
|
191
|
+
files.push({
|
|
192
|
+
path: "hooks/__agentplugins_handlers__.js",
|
|
193
|
+
content: handlersModule
|
|
194
|
+
});
|
|
195
|
+
for (const [wrapperId, { hookName }] of this.inlineWrappers.entries()) {
|
|
196
|
+
const wrapperScript = (0, import_core.generateHookWrapper)("./__agentplugins_handlers__.js", {
|
|
197
|
+
platform: "claude",
|
|
198
|
+
hookName,
|
|
199
|
+
wrapperId
|
|
200
|
+
});
|
|
201
|
+
files.push({
|
|
202
|
+
path: `hooks/${wrapperId}.js`,
|
|
203
|
+
content: wrapperScript
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
warnings.push(
|
|
207
|
+
`${inlineHandlers.size} inline handler(s) wrapped as command scripts. These require Node.js at runtime.`
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
if (plugin.skills && plugin.skills.length > 0) {
|
|
211
|
+
for (const skill of plugin.skills) {
|
|
212
|
+
const skillContent = this.buildSkillFile(skill);
|
|
213
|
+
const skillPath = `skills/${skill.name}/SKILL.md`;
|
|
214
|
+
files.push({
|
|
215
|
+
path: skillPath,
|
|
216
|
+
content: skillContent
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (plugin.mcpServers && Object.keys(plugin.mcpServers).length > 0) {
|
|
221
|
+
const mcpConfig = this.buildMCPConfig(plugin);
|
|
222
|
+
files.push({
|
|
223
|
+
path: ".mcp.json",
|
|
224
|
+
content: JSON.stringify(mcpConfig, null, 2)
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
const envScript = this.buildEnvScript(plugin);
|
|
228
|
+
if (envScript) {
|
|
229
|
+
files.push({
|
|
230
|
+
path: "hooks/__env__.sh",
|
|
231
|
+
content: envScript
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
postInstall.push(
|
|
235
|
+
`Copy the generated files to your Claude Code project root:`,
|
|
236
|
+
` cp -r .claude-plugin hooks/ skills/ .mcp.json <your-project>/`,
|
|
237
|
+
``,
|
|
238
|
+
`Environment variables available at runtime:`,
|
|
239
|
+
` \${CLAUDE_PLUGIN_ROOT} \u2014 path to the plugin directory`,
|
|
240
|
+
` \${CLAUDE_PLUGIN_DATA} \u2014 path to plugin data storage`,
|
|
241
|
+
``,
|
|
242
|
+
`Install location: ~/.claude/plugins/ or project-local .claude-plugin/`
|
|
243
|
+
);
|
|
244
|
+
if (inlineHandlers.size > 0) {
|
|
245
|
+
postInstall.push(
|
|
246
|
+
``,
|
|
247
|
+
`NOTE: ${inlineHandlers.size} inline handler(s) require Node.js to execute.`,
|
|
248
|
+
` Wrapper scripts are in hooks/*.js`
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
return {
|
|
252
|
+
files,
|
|
253
|
+
manifest: claudeManifest,
|
|
254
|
+
warnings,
|
|
255
|
+
issues: [],
|
|
256
|
+
postInstall: postInstall.length > 0 ? postInstall : void 0
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
// ─── Internal Helpers ──────────────────────────────────────────────────────
|
|
260
|
+
/**
|
|
261
|
+
* Build the Claude plugin.json manifest from a universal plugin manifest.
|
|
262
|
+
*/
|
|
263
|
+
buildManifest(plugin) {
|
|
264
|
+
const manifest = {
|
|
265
|
+
name: plugin.name,
|
|
266
|
+
version: plugin.version,
|
|
267
|
+
description: plugin.description,
|
|
268
|
+
displayName: plugin.displayName,
|
|
269
|
+
author: plugin.author,
|
|
270
|
+
homepage: plugin.homepage,
|
|
271
|
+
repository: plugin.repository,
|
|
272
|
+
license: plugin.license,
|
|
273
|
+
keywords: plugin.keywords,
|
|
274
|
+
defaultEnabled: plugin.defaultEnabled ?? true,
|
|
275
|
+
root: "${CLAUDE_PLUGIN_ROOT}",
|
|
276
|
+
data: "${CLAUDE_PLUGIN_DATA}"
|
|
277
|
+
};
|
|
278
|
+
if (plugin.userConfig) {
|
|
279
|
+
manifest.userConfig = Object.fromEntries(
|
|
280
|
+
Object.entries(plugin.userConfig).map(([key, opt]) => [
|
|
281
|
+
key,
|
|
282
|
+
{
|
|
283
|
+
type: opt.type,
|
|
284
|
+
title: opt.title,
|
|
285
|
+
description: opt.description,
|
|
286
|
+
sensitive: opt.sensitive,
|
|
287
|
+
required: opt.required,
|
|
288
|
+
default: opt.default,
|
|
289
|
+
multiple: opt.multiple
|
|
290
|
+
}
|
|
291
|
+
])
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
if (plugin.dependencies) {
|
|
295
|
+
manifest.dependencies = plugin.dependencies;
|
|
296
|
+
}
|
|
297
|
+
if (plugin.themes) {
|
|
298
|
+
manifest.themes = plugin.themes;
|
|
299
|
+
}
|
|
300
|
+
if (plugin.monitors) {
|
|
301
|
+
manifest.monitors = plugin.monitors;
|
|
302
|
+
}
|
|
303
|
+
if (plugin.lspServers) {
|
|
304
|
+
manifest.lspServers = plugin.lspServers;
|
|
305
|
+
}
|
|
306
|
+
return manifest;
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Build the hooks.json configuration from universal hooks.
|
|
310
|
+
*/
|
|
311
|
+
buildHooksConfig(plugin) {
|
|
312
|
+
if (!plugin.hooks) return [];
|
|
313
|
+
const entries = [];
|
|
314
|
+
for (const [name, def] of Object.entries(plugin.hooks)) {
|
|
315
|
+
const hookName = name;
|
|
316
|
+
if (!def) continue;
|
|
317
|
+
if (!this.supportedHooks.includes(hookName)) continue;
|
|
318
|
+
const claudeEvent = HOOK_NAME_MAP[hookName];
|
|
319
|
+
const handlerConfig = this.buildHandlerConfig(def, hookName);
|
|
320
|
+
if (handlerConfig) {
|
|
321
|
+
const entry = {
|
|
322
|
+
event: claudeEvent,
|
|
323
|
+
handler: handlerConfig
|
|
324
|
+
};
|
|
325
|
+
if (def.matcher) {
|
|
326
|
+
entry.matcher = def.matcher;
|
|
327
|
+
} else if (hookName === "preToolUse" || hookName === "postToolUse" || hookName === "postToolUseFailure") {
|
|
328
|
+
entry.matcher = "*";
|
|
329
|
+
}
|
|
330
|
+
entries.push(entry);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return entries;
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Build a single handler configuration for hooks.json.
|
|
337
|
+
*/
|
|
338
|
+
buildHandlerConfig(def, hookName) {
|
|
339
|
+
const handler = def.handler;
|
|
340
|
+
switch (handler.type) {
|
|
341
|
+
case "command": {
|
|
342
|
+
return {
|
|
343
|
+
type: "command",
|
|
344
|
+
command: handler.command,
|
|
345
|
+
shell: handler.shell,
|
|
346
|
+
statusMessage: handler.statusMessage
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
case "http": {
|
|
350
|
+
return {
|
|
351
|
+
type: "http",
|
|
352
|
+
url: handler.url,
|
|
353
|
+
headers: handler.headers
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
case "inline": {
|
|
357
|
+
const wrapperId = `__inline_${hookName}_${this.hashId(hookName)}`;
|
|
358
|
+
this.inlineWrappers.set(wrapperId, { hookName });
|
|
359
|
+
return {
|
|
360
|
+
type: "command",
|
|
361
|
+
command: `node "\${CLAUDE_PLUGIN_ROOT}/hooks/${wrapperId}.js"`,
|
|
362
|
+
shell: "bash",
|
|
363
|
+
statusMessage: `Running ${hookName} hook...`
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
default: {
|
|
367
|
+
const unknownHandler = handler;
|
|
368
|
+
if (unknownHandler.type === "mcp_tool") {
|
|
369
|
+
return {
|
|
370
|
+
type: "mcp_tool",
|
|
371
|
+
mcpTool: unknownHandler.mcpTool || unknownHandler.tool,
|
|
372
|
+
mcpServer: unknownHandler.mcpServer || unknownHandler.server
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
if (unknownHandler.type === "prompt") {
|
|
376
|
+
return {
|
|
377
|
+
type: "prompt",
|
|
378
|
+
prompt: unknownHandler.prompt
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
if (unknownHandler.type === "agent") {
|
|
382
|
+
return {
|
|
383
|
+
type: "agent",
|
|
384
|
+
agent: unknownHandler.agent
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
return null;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Build a SKILL.md file with YAML frontmatter from a universal skill definition.
|
|
393
|
+
*/
|
|
394
|
+
buildSkillFile(skill) {
|
|
395
|
+
const frontmatter = {
|
|
396
|
+
name: skill.name,
|
|
397
|
+
description: skill.description
|
|
398
|
+
};
|
|
399
|
+
if (skill.filePath && !skill.content) {
|
|
400
|
+
frontmatter.source = skill.filePath;
|
|
401
|
+
}
|
|
402
|
+
const yamlLines = Object.entries(frontmatter).map(([key, value]) => {
|
|
403
|
+
if (typeof value === "string") {
|
|
404
|
+
if (value.includes(":") || value.includes("#") || value.includes('"')) {
|
|
405
|
+
return `${key}: "${value.replace(/"/g, '\\"')}"`;
|
|
406
|
+
}
|
|
407
|
+
return `${key}: ${value}`;
|
|
408
|
+
}
|
|
409
|
+
return `${key}: ${value}`;
|
|
410
|
+
}).join("\n");
|
|
411
|
+
const frontmatterBlock = `---
|
|
412
|
+
${yamlLines}
|
|
413
|
+
---
|
|
414
|
+
`;
|
|
415
|
+
if (skill.content) {
|
|
416
|
+
if (skill.content.trimStart().startsWith("---")) {
|
|
417
|
+
return skill.content;
|
|
418
|
+
}
|
|
419
|
+
return frontmatterBlock + "\n" + skill.content;
|
|
420
|
+
}
|
|
421
|
+
if (skill.filePath) {
|
|
422
|
+
return frontmatterBlock + `
|
|
423
|
+
# ${skill.name}
|
|
424
|
+
|
|
425
|
+
See [${skill.filePath}](${skill.filePath}) for the full skill documentation.
|
|
426
|
+
`;
|
|
427
|
+
}
|
|
428
|
+
return frontmatterBlock + `
|
|
429
|
+
# ${skill.name}
|
|
430
|
+
|
|
431
|
+
${skill.description}
|
|
432
|
+
`;
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Build the .mcp.json configuration from universal MCP server definitions.
|
|
436
|
+
*/
|
|
437
|
+
buildMCPConfig(plugin) {
|
|
438
|
+
if (!plugin.mcpServers) return {};
|
|
439
|
+
const servers = {};
|
|
440
|
+
for (const [serverName, config] of Object.entries(plugin.mcpServers)) {
|
|
441
|
+
servers[serverName] = {
|
|
442
|
+
command: config.command,
|
|
443
|
+
args: config.args ?? [],
|
|
444
|
+
env: config.env ?? {},
|
|
445
|
+
// Claude uses 'stdio' by default
|
|
446
|
+
transport: config.transport ?? "stdio"
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
return { servers };
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Extract inline handlers that need wrapping, returning a map suitable
|
|
453
|
+
* for generateHandlersModule().
|
|
454
|
+
*/
|
|
455
|
+
extractInlineHandlers(plugin) {
|
|
456
|
+
const handlers = /* @__PURE__ */ new Map();
|
|
457
|
+
if (!plugin.hooks) return handlers;
|
|
458
|
+
for (const [name, def] of Object.entries(plugin.hooks)) {
|
|
459
|
+
const hookName = name;
|
|
460
|
+
if (!def || def.handler.type !== "inline") continue;
|
|
461
|
+
const inlineHandler = def.handler;
|
|
462
|
+
if (typeof inlineHandler.handler === "function") {
|
|
463
|
+
const wrapperId = `__inline_${hookName}_${this.hashId(hookName)}`;
|
|
464
|
+
handlers.set(wrapperId, inlineHandler.handler);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
return handlers;
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Build an environment setup script that exports CLAUDE_PLUGIN_ROOT and
|
|
471
|
+
* CLAUDE_PLUGIN_DATA for use by hook scripts.
|
|
472
|
+
*/
|
|
473
|
+
buildEnvScript(plugin) {
|
|
474
|
+
if (!plugin.hooks) return null;
|
|
475
|
+
const hasCommandHandlers = Object.values(plugin.hooks).some(
|
|
476
|
+
(def) => def?.handler.type === "command" || def?.handler.type === "inline"
|
|
477
|
+
);
|
|
478
|
+
if (!hasCommandHandlers) return null;
|
|
479
|
+
return `#!/usr/bin/env bash
|
|
480
|
+
# AgentPlugins Auto-Generated Environment Setup for Claude Code
|
|
481
|
+
# Plugin: ${plugin.name}
|
|
482
|
+
# DO NOT EDIT \u2014 This file is regenerated on each build.
|
|
483
|
+
|
|
484
|
+
# Resolve plugin root relative to this script
|
|
485
|
+
export CLAUDE_PLUGIN_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
486
|
+
export CLAUDE_PLUGIN_DATA="\${CLAUDE_PLUGIN_ROOT}/.data"
|
|
487
|
+
|
|
488
|
+
# Ensure data directory exists
|
|
489
|
+
mkdir -p "\${CLAUDE_PLUGIN_DATA}"
|
|
490
|
+
`;
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Generate a short hash for a hook name to use in wrapper script filenames.
|
|
494
|
+
*/
|
|
495
|
+
hashId(input) {
|
|
496
|
+
let hash = 0;
|
|
497
|
+
for (let i = 0; i < input.length; i++) {
|
|
498
|
+
const char = input.charCodeAt(i);
|
|
499
|
+
hash = (hash << 5) - hash + char;
|
|
500
|
+
hash |= 0;
|
|
501
|
+
}
|
|
502
|
+
return Math.abs(hash).toString(36).slice(0, 6);
|
|
503
|
+
}
|
|
504
|
+
};
|
|
505
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
506
|
+
0 && (module.exports = {
|
|
507
|
+
createAdapter,
|
|
508
|
+
createClaudeAdapter
|
|
509
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { PlatformAdapter } from '@agentplugins/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* AgentPlugins Claude Code Adapter
|
|
5
|
+
*
|
|
6
|
+
* Compiles universal AgentPlugins plugins into Claude Code's native plugin format.
|
|
7
|
+
*
|
|
8
|
+
* Claude Code uses:
|
|
9
|
+
* - `.claude-plugin/plugin.json` — manifest
|
|
10
|
+
* - `hooks/hooks.json` — hook configuration with matchers
|
|
11
|
+
* - `skills/<name>/SKILL.md` — skill documentation (YAML frontmatter + markdown)
|
|
12
|
+
* - `.mcp.json` — MCP server configurations
|
|
13
|
+
* - `${CLAUDE_PLUGIN_ROOT}` and `${CLAUDE_PLUGIN_DATA}` — env var placeholders
|
|
14
|
+
*
|
|
15
|
+
* Handler mapping:
|
|
16
|
+
* - command → native command handler
|
|
17
|
+
* - http → native HTTP handler
|
|
18
|
+
* - mcp_tool → Claude's MCP tool handler (emitted in hooks.json)
|
|
19
|
+
* - prompt → Claude's prompt handler (emitted in hooks.json)
|
|
20
|
+
* - agent → Claude's agent handler (emitted in hooks.json)
|
|
21
|
+
* - inline → auto-wrapped as Node.js command script
|
|
22
|
+
*
|
|
23
|
+
* All 19 universal hooks are supported by Claude Code.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/** Claude-specific handler types beyond the universal ones. */
|
|
27
|
+
type ClaudeHandlerType = 'command' | 'http' | 'mcp_tool' | 'prompt' | 'agent';
|
|
28
|
+
/** Claude plugin manifest shape. */
|
|
29
|
+
interface ClaudePluginManifest {
|
|
30
|
+
name: string;
|
|
31
|
+
version: string;
|
|
32
|
+
description: string;
|
|
33
|
+
displayName?: string;
|
|
34
|
+
author?: string | {
|
|
35
|
+
name: string;
|
|
36
|
+
email?: string;
|
|
37
|
+
url?: string;
|
|
38
|
+
};
|
|
39
|
+
homepage?: string;
|
|
40
|
+
repository?: string;
|
|
41
|
+
license?: string;
|
|
42
|
+
keywords?: string[];
|
|
43
|
+
defaultEnabled?: boolean;
|
|
44
|
+
/** Claude-specific: plugin root directory env var */
|
|
45
|
+
root?: string;
|
|
46
|
+
/** Claude-specific: plugin data directory env var */
|
|
47
|
+
data?: string;
|
|
48
|
+
/** User configuration schema */
|
|
49
|
+
userConfig?: Record<string, ClaudeUserConfigField>;
|
|
50
|
+
/** Dependencies on other plugins */
|
|
51
|
+
dependencies?: string[];
|
|
52
|
+
/** Theme configuration */
|
|
53
|
+
themes?: ClaudeThemeConfig[];
|
|
54
|
+
/** File monitors */
|
|
55
|
+
monitors?: ClaudeMonitorConfig[];
|
|
56
|
+
/** LSP server configuration */
|
|
57
|
+
lspServers?: ClaudeLSPServerConfig[];
|
|
58
|
+
/** Extra metadata allowed by Claude */
|
|
59
|
+
[key: string]: unknown;
|
|
60
|
+
}
|
|
61
|
+
/** Claude user config field definition. */
|
|
62
|
+
interface ClaudeUserConfigField {
|
|
63
|
+
type: 'string' | 'number' | 'boolean' | 'directory' | 'file';
|
|
64
|
+
title: string;
|
|
65
|
+
description: string;
|
|
66
|
+
sensitive?: boolean;
|
|
67
|
+
required?: boolean;
|
|
68
|
+
default?: unknown;
|
|
69
|
+
multiple?: boolean;
|
|
70
|
+
}
|
|
71
|
+
/** Claude theme configuration. */
|
|
72
|
+
interface ClaudeThemeConfig {
|
|
73
|
+
name: string;
|
|
74
|
+
path: string;
|
|
75
|
+
}
|
|
76
|
+
/** Claude file monitor configuration. */
|
|
77
|
+
interface ClaudeMonitorConfig {
|
|
78
|
+
pattern: string;
|
|
79
|
+
event: string;
|
|
80
|
+
}
|
|
81
|
+
/** Claude LSP server configuration. */
|
|
82
|
+
interface ClaudeLSPServerConfig {
|
|
83
|
+
name: string;
|
|
84
|
+
command: string;
|
|
85
|
+
args?: string[];
|
|
86
|
+
}
|
|
87
|
+
/** Hook entry in Claude's hooks.json. */
|
|
88
|
+
interface ClaudeHookEntry {
|
|
89
|
+
event: string;
|
|
90
|
+
matcher?: string;
|
|
91
|
+
handler: ClaudeHandlerConfig;
|
|
92
|
+
}
|
|
93
|
+
/** Handler configuration in hooks.json. */
|
|
94
|
+
interface ClaudeHandlerConfig {
|
|
95
|
+
type: ClaudeHandlerType;
|
|
96
|
+
command?: string;
|
|
97
|
+
shell?: string;
|
|
98
|
+
statusMessage?: string;
|
|
99
|
+
url?: string;
|
|
100
|
+
headers?: Record<string, string>;
|
|
101
|
+
mcpTool?: string;
|
|
102
|
+
mcpServer?: string;
|
|
103
|
+
prompt?: string;
|
|
104
|
+
agent?: string;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Create a Claude Code platform adapter.
|
|
108
|
+
*
|
|
109
|
+
* Usage:
|
|
110
|
+
* ```ts
|
|
111
|
+
* import { createAdapter } from '@agentplugins/adapter-claude';
|
|
112
|
+
* const adapter = createAdapter();
|
|
113
|
+
* const output = adapter.compile(manifest);
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
116
|
+
declare function createClaudeAdapter(): PlatformAdapter;
|
|
117
|
+
/** @deprecated Use createClaudeAdapter instead */
|
|
118
|
+
declare function createAdapter(): PlatformAdapter;
|
|
119
|
+
|
|
120
|
+
export { type ClaudeHandlerConfig, type ClaudeHookEntry, type ClaudeLSPServerConfig, type ClaudeMonitorConfig, type ClaudePluginManifest, type ClaudeThemeConfig, type ClaudeUserConfigField, createAdapter, createClaudeAdapter, createClaudeAdapter as default };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { PlatformAdapter } from '@agentplugins/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* AgentPlugins Claude Code Adapter
|
|
5
|
+
*
|
|
6
|
+
* Compiles universal AgentPlugins plugins into Claude Code's native plugin format.
|
|
7
|
+
*
|
|
8
|
+
* Claude Code uses:
|
|
9
|
+
* - `.claude-plugin/plugin.json` — manifest
|
|
10
|
+
* - `hooks/hooks.json` — hook configuration with matchers
|
|
11
|
+
* - `skills/<name>/SKILL.md` — skill documentation (YAML frontmatter + markdown)
|
|
12
|
+
* - `.mcp.json` — MCP server configurations
|
|
13
|
+
* - `${CLAUDE_PLUGIN_ROOT}` and `${CLAUDE_PLUGIN_DATA}` — env var placeholders
|
|
14
|
+
*
|
|
15
|
+
* Handler mapping:
|
|
16
|
+
* - command → native command handler
|
|
17
|
+
* - http → native HTTP handler
|
|
18
|
+
* - mcp_tool → Claude's MCP tool handler (emitted in hooks.json)
|
|
19
|
+
* - prompt → Claude's prompt handler (emitted in hooks.json)
|
|
20
|
+
* - agent → Claude's agent handler (emitted in hooks.json)
|
|
21
|
+
* - inline → auto-wrapped as Node.js command script
|
|
22
|
+
*
|
|
23
|
+
* All 19 universal hooks are supported by Claude Code.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/** Claude-specific handler types beyond the universal ones. */
|
|
27
|
+
type ClaudeHandlerType = 'command' | 'http' | 'mcp_tool' | 'prompt' | 'agent';
|
|
28
|
+
/** Claude plugin manifest shape. */
|
|
29
|
+
interface ClaudePluginManifest {
|
|
30
|
+
name: string;
|
|
31
|
+
version: string;
|
|
32
|
+
description: string;
|
|
33
|
+
displayName?: string;
|
|
34
|
+
author?: string | {
|
|
35
|
+
name: string;
|
|
36
|
+
email?: string;
|
|
37
|
+
url?: string;
|
|
38
|
+
};
|
|
39
|
+
homepage?: string;
|
|
40
|
+
repository?: string;
|
|
41
|
+
license?: string;
|
|
42
|
+
keywords?: string[];
|
|
43
|
+
defaultEnabled?: boolean;
|
|
44
|
+
/** Claude-specific: plugin root directory env var */
|
|
45
|
+
root?: string;
|
|
46
|
+
/** Claude-specific: plugin data directory env var */
|
|
47
|
+
data?: string;
|
|
48
|
+
/** User configuration schema */
|
|
49
|
+
userConfig?: Record<string, ClaudeUserConfigField>;
|
|
50
|
+
/** Dependencies on other plugins */
|
|
51
|
+
dependencies?: string[];
|
|
52
|
+
/** Theme configuration */
|
|
53
|
+
themes?: ClaudeThemeConfig[];
|
|
54
|
+
/** File monitors */
|
|
55
|
+
monitors?: ClaudeMonitorConfig[];
|
|
56
|
+
/** LSP server configuration */
|
|
57
|
+
lspServers?: ClaudeLSPServerConfig[];
|
|
58
|
+
/** Extra metadata allowed by Claude */
|
|
59
|
+
[key: string]: unknown;
|
|
60
|
+
}
|
|
61
|
+
/** Claude user config field definition. */
|
|
62
|
+
interface ClaudeUserConfigField {
|
|
63
|
+
type: 'string' | 'number' | 'boolean' | 'directory' | 'file';
|
|
64
|
+
title: string;
|
|
65
|
+
description: string;
|
|
66
|
+
sensitive?: boolean;
|
|
67
|
+
required?: boolean;
|
|
68
|
+
default?: unknown;
|
|
69
|
+
multiple?: boolean;
|
|
70
|
+
}
|
|
71
|
+
/** Claude theme configuration. */
|
|
72
|
+
interface ClaudeThemeConfig {
|
|
73
|
+
name: string;
|
|
74
|
+
path: string;
|
|
75
|
+
}
|
|
76
|
+
/** Claude file monitor configuration. */
|
|
77
|
+
interface ClaudeMonitorConfig {
|
|
78
|
+
pattern: string;
|
|
79
|
+
event: string;
|
|
80
|
+
}
|
|
81
|
+
/** Claude LSP server configuration. */
|
|
82
|
+
interface ClaudeLSPServerConfig {
|
|
83
|
+
name: string;
|
|
84
|
+
command: string;
|
|
85
|
+
args?: string[];
|
|
86
|
+
}
|
|
87
|
+
/** Hook entry in Claude's hooks.json. */
|
|
88
|
+
interface ClaudeHookEntry {
|
|
89
|
+
event: string;
|
|
90
|
+
matcher?: string;
|
|
91
|
+
handler: ClaudeHandlerConfig;
|
|
92
|
+
}
|
|
93
|
+
/** Handler configuration in hooks.json. */
|
|
94
|
+
interface ClaudeHandlerConfig {
|
|
95
|
+
type: ClaudeHandlerType;
|
|
96
|
+
command?: string;
|
|
97
|
+
shell?: string;
|
|
98
|
+
statusMessage?: string;
|
|
99
|
+
url?: string;
|
|
100
|
+
headers?: Record<string, string>;
|
|
101
|
+
mcpTool?: string;
|
|
102
|
+
mcpServer?: string;
|
|
103
|
+
prompt?: string;
|
|
104
|
+
agent?: string;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Create a Claude Code platform adapter.
|
|
108
|
+
*
|
|
109
|
+
* Usage:
|
|
110
|
+
* ```ts
|
|
111
|
+
* import { createAdapter } from '@agentplugins/adapter-claude';
|
|
112
|
+
* const adapter = createAdapter();
|
|
113
|
+
* const output = adapter.compile(manifest);
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
116
|
+
declare function createClaudeAdapter(): PlatformAdapter;
|
|
117
|
+
/** @deprecated Use createClaudeAdapter instead */
|
|
118
|
+
declare function createAdapter(): PlatformAdapter;
|
|
119
|
+
|
|
120
|
+
export { type ClaudeHandlerConfig, type ClaudeHookEntry, type ClaudeLSPServerConfig, type ClaudeMonitorConfig, type ClaudePluginManifest, type ClaudeThemeConfig, type ClaudeUserConfigField, createAdapter, createClaudeAdapter, createClaudeAdapter as default };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import {
|
|
3
|
+
generateHookWrapper,
|
|
4
|
+
generateHandlersModule,
|
|
5
|
+
validateForPlatform,
|
|
6
|
+
Severity
|
|
7
|
+
} from "@agentplugins/core";
|
|
8
|
+
var CLAUDE_SUPPORTED_HOOKS = [
|
|
9
|
+
"sessionStart",
|
|
10
|
+
"sessionEnd",
|
|
11
|
+
"userPromptSubmit",
|
|
12
|
+
"userPromptExpansion",
|
|
13
|
+
"preToolUse",
|
|
14
|
+
"postToolUse",
|
|
15
|
+
"postToolUseFailure",
|
|
16
|
+
"permissionRequest",
|
|
17
|
+
"permissionDenied",
|
|
18
|
+
"subagentStart",
|
|
19
|
+
"subagentStop",
|
|
20
|
+
"preCompact",
|
|
21
|
+
"postCompact",
|
|
22
|
+
"stop",
|
|
23
|
+
"stopFailure",
|
|
24
|
+
"notification",
|
|
25
|
+
"fileChanged",
|
|
26
|
+
"cwdChanged",
|
|
27
|
+
"setup"
|
|
28
|
+
];
|
|
29
|
+
var CLAUDE_SUPPORTED_HANDLERS = [
|
|
30
|
+
"command",
|
|
31
|
+
"http",
|
|
32
|
+
"inline"
|
|
33
|
+
// auto-wrapped as command scripts
|
|
34
|
+
];
|
|
35
|
+
var HOOK_NAME_MAP = {
|
|
36
|
+
sessionStart: "SessionStart",
|
|
37
|
+
sessionEnd: "SessionEnd",
|
|
38
|
+
userPromptSubmit: "UserPromptSubmit",
|
|
39
|
+
userPromptExpansion: "UserPromptExpansion",
|
|
40
|
+
preToolUse: "PreToolUse",
|
|
41
|
+
postToolUse: "PostToolUse",
|
|
42
|
+
postToolUseFailure: "PostToolUseFailure",
|
|
43
|
+
permissionRequest: "PermissionRequest",
|
|
44
|
+
permissionDenied: "PermissionDenied",
|
|
45
|
+
subagentStart: "SubagentStart",
|
|
46
|
+
subagentStop: "SubagentStop",
|
|
47
|
+
preCompact: "PreCompact",
|
|
48
|
+
postCompact: "PostCompact",
|
|
49
|
+
stop: "Stop",
|
|
50
|
+
stopFailure: "StopFailure",
|
|
51
|
+
notification: "Notification",
|
|
52
|
+
fileChanged: "FileChanged",
|
|
53
|
+
cwdChanged: "CwdChanged",
|
|
54
|
+
setup: "Setup"
|
|
55
|
+
};
|
|
56
|
+
function createClaudeAdapter() {
|
|
57
|
+
return new ClaudePlatformAdapter();
|
|
58
|
+
}
|
|
59
|
+
function createAdapter() {
|
|
60
|
+
return createClaudeAdapter();
|
|
61
|
+
}
|
|
62
|
+
var ClaudePlatformAdapter = class {
|
|
63
|
+
name = "claude";
|
|
64
|
+
displayName = "Claude Code";
|
|
65
|
+
supportedHooks = CLAUDE_SUPPORTED_HOOKS;
|
|
66
|
+
supportedHandlers = CLAUDE_SUPPORTED_HANDLERS;
|
|
67
|
+
manifestPath = ".claude-plugin/plugin.json";
|
|
68
|
+
manifestFormat = "json";
|
|
69
|
+
// Inline handlers are wrapped as command scripts; tracks wrapper ID -> hook name
|
|
70
|
+
inlineWrappers = /* @__PURE__ */ new Map();
|
|
71
|
+
// ─── Validation ────────────────────────────────────────────────────────────
|
|
72
|
+
validate(plugin) {
|
|
73
|
+
const issues = [...validateForPlatform(plugin, "claude")];
|
|
74
|
+
if (plugin.hooks) {
|
|
75
|
+
for (const [name, def] of Object.entries(plugin.hooks)) {
|
|
76
|
+
const hookName = name;
|
|
77
|
+
if (!def) continue;
|
|
78
|
+
if (def.handler.type === "inline") {
|
|
79
|
+
const handler = def.handler;
|
|
80
|
+
if (typeof handler.handler !== "function") {
|
|
81
|
+
issues.push({
|
|
82
|
+
severity: Severity.ERROR,
|
|
83
|
+
field: `hooks.${hookName}.handler`,
|
|
84
|
+
message: "Inline handler must be a function"
|
|
85
|
+
});
|
|
86
|
+
} else {
|
|
87
|
+
issues.push({
|
|
88
|
+
severity: Severity.INFO,
|
|
89
|
+
field: `hooks.${hookName}.handler`,
|
|
90
|
+
message: "Inline handler will be auto-wrapped as a Claude command script",
|
|
91
|
+
suggestion: 'For better performance, use a "command" or "http" handler'
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (def.matcher && ["preToolUse", "postToolUse", "postToolUseFailure"].includes(hookName)) {
|
|
96
|
+
if (def.matcher.includes("*") && !def.matcher.match(/^\*?\w+\*?$/)) {
|
|
97
|
+
issues.push({
|
|
98
|
+
severity: Severity.WARNING,
|
|
99
|
+
field: `hooks.${hookName}.matcher`,
|
|
100
|
+
message: `Matcher pattern "${def.matcher}" may not be supported by Claude for tool hooks`,
|
|
101
|
+
suggestion: 'Use simple patterns like "toolName", "prefix*", or "*suffix"'
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (plugin.mcpServers) {
|
|
108
|
+
for (const [serverName, config] of Object.entries(plugin.mcpServers)) {
|
|
109
|
+
if (!config.command) {
|
|
110
|
+
issues.push({
|
|
111
|
+
severity: Severity.ERROR,
|
|
112
|
+
field: `mcpServers.${serverName}.command`,
|
|
113
|
+
message: `MCP server "${serverName}" requires a command`
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
if (config.transport && config.transport !== "stdio" && config.transport !== "http") {
|
|
117
|
+
issues.push({
|
|
118
|
+
severity: Severity.WARNING,
|
|
119
|
+
field: `mcpServers.${serverName}.transport`,
|
|
120
|
+
message: `Transport "${config.transport}" may not be supported by Claude MCP`,
|
|
121
|
+
suggestion: 'Use "stdio" (default) or "http"'
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (plugin.skills) {
|
|
127
|
+
for (let i = 0; i < plugin.skills.length; i++) {
|
|
128
|
+
const skill = plugin.skills[i];
|
|
129
|
+
if (skill.content && skill.filePath) {
|
|
130
|
+
issues.push({
|
|
131
|
+
severity: Severity.WARNING,
|
|
132
|
+
field: `skills[${i}]`,
|
|
133
|
+
message: `Skill "${skill.name}" has both content and filePath \u2014 content takes precedence`
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (plugin.userConfig) {
|
|
139
|
+
const supportedTypes = ["string", "number", "boolean", "directory", "file"];
|
|
140
|
+
for (const [key, opt] of Object.entries(plugin.userConfig)) {
|
|
141
|
+
if (!supportedTypes.includes(opt.type)) {
|
|
142
|
+
issues.push({
|
|
143
|
+
severity: Severity.WARNING,
|
|
144
|
+
field: `userConfig.${key}.type`,
|
|
145
|
+
message: `User config type "${opt.type}" may not be fully supported by Claude`,
|
|
146
|
+
suggestion: `Supported types: ${supportedTypes.join(", ")}`
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return issues;
|
|
152
|
+
}
|
|
153
|
+
// ─── Compilation ───────────────────────────────────────────────────────────
|
|
154
|
+
compile(plugin) {
|
|
155
|
+
this.inlineWrappers.clear();
|
|
156
|
+
const files = [];
|
|
157
|
+
const warnings = [];
|
|
158
|
+
const postInstall = [];
|
|
159
|
+
const claudeManifest = this.buildManifest(plugin);
|
|
160
|
+
const hooksConfig = this.buildHooksConfig(plugin);
|
|
161
|
+
if (hooksConfig.length > 0) {
|
|
162
|
+
files.push({
|
|
163
|
+
path: "hooks/hooks.json",
|
|
164
|
+
content: JSON.stringify(hooksConfig, null, 2)
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
const inlineHandlers = this.extractInlineHandlers(plugin);
|
|
168
|
+
if (inlineHandlers.size > 0) {
|
|
169
|
+
const handlersModule = generateHandlersModule(inlineHandlers);
|
|
170
|
+
files.push({
|
|
171
|
+
path: "hooks/__agentplugins_handlers__.js",
|
|
172
|
+
content: handlersModule
|
|
173
|
+
});
|
|
174
|
+
for (const [wrapperId, { hookName }] of this.inlineWrappers.entries()) {
|
|
175
|
+
const wrapperScript = generateHookWrapper("./__agentplugins_handlers__.js", {
|
|
176
|
+
platform: "claude",
|
|
177
|
+
hookName,
|
|
178
|
+
wrapperId
|
|
179
|
+
});
|
|
180
|
+
files.push({
|
|
181
|
+
path: `hooks/${wrapperId}.js`,
|
|
182
|
+
content: wrapperScript
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
warnings.push(
|
|
186
|
+
`${inlineHandlers.size} inline handler(s) wrapped as command scripts. These require Node.js at runtime.`
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
if (plugin.skills && plugin.skills.length > 0) {
|
|
190
|
+
for (const skill of plugin.skills) {
|
|
191
|
+
const skillContent = this.buildSkillFile(skill);
|
|
192
|
+
const skillPath = `skills/${skill.name}/SKILL.md`;
|
|
193
|
+
files.push({
|
|
194
|
+
path: skillPath,
|
|
195
|
+
content: skillContent
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (plugin.mcpServers && Object.keys(plugin.mcpServers).length > 0) {
|
|
200
|
+
const mcpConfig = this.buildMCPConfig(plugin);
|
|
201
|
+
files.push({
|
|
202
|
+
path: ".mcp.json",
|
|
203
|
+
content: JSON.stringify(mcpConfig, null, 2)
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
const envScript = this.buildEnvScript(plugin);
|
|
207
|
+
if (envScript) {
|
|
208
|
+
files.push({
|
|
209
|
+
path: "hooks/__env__.sh",
|
|
210
|
+
content: envScript
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
postInstall.push(
|
|
214
|
+
`Copy the generated files to your Claude Code project root:`,
|
|
215
|
+
` cp -r .claude-plugin hooks/ skills/ .mcp.json <your-project>/`,
|
|
216
|
+
``,
|
|
217
|
+
`Environment variables available at runtime:`,
|
|
218
|
+
` \${CLAUDE_PLUGIN_ROOT} \u2014 path to the plugin directory`,
|
|
219
|
+
` \${CLAUDE_PLUGIN_DATA} \u2014 path to plugin data storage`,
|
|
220
|
+
``,
|
|
221
|
+
`Install location: ~/.claude/plugins/ or project-local .claude-plugin/`
|
|
222
|
+
);
|
|
223
|
+
if (inlineHandlers.size > 0) {
|
|
224
|
+
postInstall.push(
|
|
225
|
+
``,
|
|
226
|
+
`NOTE: ${inlineHandlers.size} inline handler(s) require Node.js to execute.`,
|
|
227
|
+
` Wrapper scripts are in hooks/*.js`
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
return {
|
|
231
|
+
files,
|
|
232
|
+
manifest: claudeManifest,
|
|
233
|
+
warnings,
|
|
234
|
+
issues: [],
|
|
235
|
+
postInstall: postInstall.length > 0 ? postInstall : void 0
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
// ─── Internal Helpers ──────────────────────────────────────────────────────
|
|
239
|
+
/**
|
|
240
|
+
* Build the Claude plugin.json manifest from a universal plugin manifest.
|
|
241
|
+
*/
|
|
242
|
+
buildManifest(plugin) {
|
|
243
|
+
const manifest = {
|
|
244
|
+
name: plugin.name,
|
|
245
|
+
version: plugin.version,
|
|
246
|
+
description: plugin.description,
|
|
247
|
+
displayName: plugin.displayName,
|
|
248
|
+
author: plugin.author,
|
|
249
|
+
homepage: plugin.homepage,
|
|
250
|
+
repository: plugin.repository,
|
|
251
|
+
license: plugin.license,
|
|
252
|
+
keywords: plugin.keywords,
|
|
253
|
+
defaultEnabled: plugin.defaultEnabled ?? true,
|
|
254
|
+
root: "${CLAUDE_PLUGIN_ROOT}",
|
|
255
|
+
data: "${CLAUDE_PLUGIN_DATA}"
|
|
256
|
+
};
|
|
257
|
+
if (plugin.userConfig) {
|
|
258
|
+
manifest.userConfig = Object.fromEntries(
|
|
259
|
+
Object.entries(plugin.userConfig).map(([key, opt]) => [
|
|
260
|
+
key,
|
|
261
|
+
{
|
|
262
|
+
type: opt.type,
|
|
263
|
+
title: opt.title,
|
|
264
|
+
description: opt.description,
|
|
265
|
+
sensitive: opt.sensitive,
|
|
266
|
+
required: opt.required,
|
|
267
|
+
default: opt.default,
|
|
268
|
+
multiple: opt.multiple
|
|
269
|
+
}
|
|
270
|
+
])
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
if (plugin.dependencies) {
|
|
274
|
+
manifest.dependencies = plugin.dependencies;
|
|
275
|
+
}
|
|
276
|
+
if (plugin.themes) {
|
|
277
|
+
manifest.themes = plugin.themes;
|
|
278
|
+
}
|
|
279
|
+
if (plugin.monitors) {
|
|
280
|
+
manifest.monitors = plugin.monitors;
|
|
281
|
+
}
|
|
282
|
+
if (plugin.lspServers) {
|
|
283
|
+
manifest.lspServers = plugin.lspServers;
|
|
284
|
+
}
|
|
285
|
+
return manifest;
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Build the hooks.json configuration from universal hooks.
|
|
289
|
+
*/
|
|
290
|
+
buildHooksConfig(plugin) {
|
|
291
|
+
if (!plugin.hooks) return [];
|
|
292
|
+
const entries = [];
|
|
293
|
+
for (const [name, def] of Object.entries(plugin.hooks)) {
|
|
294
|
+
const hookName = name;
|
|
295
|
+
if (!def) continue;
|
|
296
|
+
if (!this.supportedHooks.includes(hookName)) continue;
|
|
297
|
+
const claudeEvent = HOOK_NAME_MAP[hookName];
|
|
298
|
+
const handlerConfig = this.buildHandlerConfig(def, hookName);
|
|
299
|
+
if (handlerConfig) {
|
|
300
|
+
const entry = {
|
|
301
|
+
event: claudeEvent,
|
|
302
|
+
handler: handlerConfig
|
|
303
|
+
};
|
|
304
|
+
if (def.matcher) {
|
|
305
|
+
entry.matcher = def.matcher;
|
|
306
|
+
} else if (hookName === "preToolUse" || hookName === "postToolUse" || hookName === "postToolUseFailure") {
|
|
307
|
+
entry.matcher = "*";
|
|
308
|
+
}
|
|
309
|
+
entries.push(entry);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return entries;
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Build a single handler configuration for hooks.json.
|
|
316
|
+
*/
|
|
317
|
+
buildHandlerConfig(def, hookName) {
|
|
318
|
+
const handler = def.handler;
|
|
319
|
+
switch (handler.type) {
|
|
320
|
+
case "command": {
|
|
321
|
+
return {
|
|
322
|
+
type: "command",
|
|
323
|
+
command: handler.command,
|
|
324
|
+
shell: handler.shell,
|
|
325
|
+
statusMessage: handler.statusMessage
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
case "http": {
|
|
329
|
+
return {
|
|
330
|
+
type: "http",
|
|
331
|
+
url: handler.url,
|
|
332
|
+
headers: handler.headers
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
case "inline": {
|
|
336
|
+
const wrapperId = `__inline_${hookName}_${this.hashId(hookName)}`;
|
|
337
|
+
this.inlineWrappers.set(wrapperId, { hookName });
|
|
338
|
+
return {
|
|
339
|
+
type: "command",
|
|
340
|
+
command: `node "\${CLAUDE_PLUGIN_ROOT}/hooks/${wrapperId}.js"`,
|
|
341
|
+
shell: "bash",
|
|
342
|
+
statusMessage: `Running ${hookName} hook...`
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
default: {
|
|
346
|
+
const unknownHandler = handler;
|
|
347
|
+
if (unknownHandler.type === "mcp_tool") {
|
|
348
|
+
return {
|
|
349
|
+
type: "mcp_tool",
|
|
350
|
+
mcpTool: unknownHandler.mcpTool || unknownHandler.tool,
|
|
351
|
+
mcpServer: unknownHandler.mcpServer || unknownHandler.server
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
if (unknownHandler.type === "prompt") {
|
|
355
|
+
return {
|
|
356
|
+
type: "prompt",
|
|
357
|
+
prompt: unknownHandler.prompt
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
if (unknownHandler.type === "agent") {
|
|
361
|
+
return {
|
|
362
|
+
type: "agent",
|
|
363
|
+
agent: unknownHandler.agent
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Build a SKILL.md file with YAML frontmatter from a universal skill definition.
|
|
372
|
+
*/
|
|
373
|
+
buildSkillFile(skill) {
|
|
374
|
+
const frontmatter = {
|
|
375
|
+
name: skill.name,
|
|
376
|
+
description: skill.description
|
|
377
|
+
};
|
|
378
|
+
if (skill.filePath && !skill.content) {
|
|
379
|
+
frontmatter.source = skill.filePath;
|
|
380
|
+
}
|
|
381
|
+
const yamlLines = Object.entries(frontmatter).map(([key, value]) => {
|
|
382
|
+
if (typeof value === "string") {
|
|
383
|
+
if (value.includes(":") || value.includes("#") || value.includes('"')) {
|
|
384
|
+
return `${key}: "${value.replace(/"/g, '\\"')}"`;
|
|
385
|
+
}
|
|
386
|
+
return `${key}: ${value}`;
|
|
387
|
+
}
|
|
388
|
+
return `${key}: ${value}`;
|
|
389
|
+
}).join("\n");
|
|
390
|
+
const frontmatterBlock = `---
|
|
391
|
+
${yamlLines}
|
|
392
|
+
---
|
|
393
|
+
`;
|
|
394
|
+
if (skill.content) {
|
|
395
|
+
if (skill.content.trimStart().startsWith("---")) {
|
|
396
|
+
return skill.content;
|
|
397
|
+
}
|
|
398
|
+
return frontmatterBlock + "\n" + skill.content;
|
|
399
|
+
}
|
|
400
|
+
if (skill.filePath) {
|
|
401
|
+
return frontmatterBlock + `
|
|
402
|
+
# ${skill.name}
|
|
403
|
+
|
|
404
|
+
See [${skill.filePath}](${skill.filePath}) for the full skill documentation.
|
|
405
|
+
`;
|
|
406
|
+
}
|
|
407
|
+
return frontmatterBlock + `
|
|
408
|
+
# ${skill.name}
|
|
409
|
+
|
|
410
|
+
${skill.description}
|
|
411
|
+
`;
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Build the .mcp.json configuration from universal MCP server definitions.
|
|
415
|
+
*/
|
|
416
|
+
buildMCPConfig(plugin) {
|
|
417
|
+
if (!plugin.mcpServers) return {};
|
|
418
|
+
const servers = {};
|
|
419
|
+
for (const [serverName, config] of Object.entries(plugin.mcpServers)) {
|
|
420
|
+
servers[serverName] = {
|
|
421
|
+
command: config.command,
|
|
422
|
+
args: config.args ?? [],
|
|
423
|
+
env: config.env ?? {},
|
|
424
|
+
// Claude uses 'stdio' by default
|
|
425
|
+
transport: config.transport ?? "stdio"
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
return { servers };
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Extract inline handlers that need wrapping, returning a map suitable
|
|
432
|
+
* for generateHandlersModule().
|
|
433
|
+
*/
|
|
434
|
+
extractInlineHandlers(plugin) {
|
|
435
|
+
const handlers = /* @__PURE__ */ new Map();
|
|
436
|
+
if (!plugin.hooks) return handlers;
|
|
437
|
+
for (const [name, def] of Object.entries(plugin.hooks)) {
|
|
438
|
+
const hookName = name;
|
|
439
|
+
if (!def || def.handler.type !== "inline") continue;
|
|
440
|
+
const inlineHandler = def.handler;
|
|
441
|
+
if (typeof inlineHandler.handler === "function") {
|
|
442
|
+
const wrapperId = `__inline_${hookName}_${this.hashId(hookName)}`;
|
|
443
|
+
handlers.set(wrapperId, inlineHandler.handler);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
return handlers;
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Build an environment setup script that exports CLAUDE_PLUGIN_ROOT and
|
|
450
|
+
* CLAUDE_PLUGIN_DATA for use by hook scripts.
|
|
451
|
+
*/
|
|
452
|
+
buildEnvScript(plugin) {
|
|
453
|
+
if (!plugin.hooks) return null;
|
|
454
|
+
const hasCommandHandlers = Object.values(plugin.hooks).some(
|
|
455
|
+
(def) => def?.handler.type === "command" || def?.handler.type === "inline"
|
|
456
|
+
);
|
|
457
|
+
if (!hasCommandHandlers) return null;
|
|
458
|
+
return `#!/usr/bin/env bash
|
|
459
|
+
# AgentPlugins Auto-Generated Environment Setup for Claude Code
|
|
460
|
+
# Plugin: ${plugin.name}
|
|
461
|
+
# DO NOT EDIT \u2014 This file is regenerated on each build.
|
|
462
|
+
|
|
463
|
+
# Resolve plugin root relative to this script
|
|
464
|
+
export CLAUDE_PLUGIN_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
465
|
+
export CLAUDE_PLUGIN_DATA="\${CLAUDE_PLUGIN_ROOT}/.data"
|
|
466
|
+
|
|
467
|
+
# Ensure data directory exists
|
|
468
|
+
mkdir -p "\${CLAUDE_PLUGIN_DATA}"
|
|
469
|
+
`;
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* Generate a short hash for a hook name to use in wrapper script filenames.
|
|
473
|
+
*/
|
|
474
|
+
hashId(input) {
|
|
475
|
+
let hash = 0;
|
|
476
|
+
for (let i = 0; i < input.length; i++) {
|
|
477
|
+
const char = input.charCodeAt(i);
|
|
478
|
+
hash = (hash << 5) - hash + char;
|
|
479
|
+
hash |= 0;
|
|
480
|
+
}
|
|
481
|
+
return Math.abs(hash).toString(36).slice(0, 6);
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
export {
|
|
485
|
+
createAdapter,
|
|
486
|
+
createClaudeAdapter,
|
|
487
|
+
createClaudeAdapter as default
|
|
488
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@agentplugins/adapter-claude",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "AgentPlugins platform adapter for Claude Code",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"require": "./dist/index.cjs",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsup src/index.ts --format cjs,esm --dts",
|
|
17
|
+
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
|
|
18
|
+
"typecheck": "tsc --noEmit"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@agentplugins/core": "workspace:*"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/node": "^20.0.0",
|
|
25
|
+
"tsup": "^8.0.0",
|
|
26
|
+
"typescript": "^5.5.0"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"dist",
|
|
30
|
+
"README.md"
|
|
31
|
+
],
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
},
|
|
35
|
+
"license": "MIT"
|
|
36
|
+
}
|