60
&& isURLEquals(a.sourceUri, b.sourceUri);
61
}
63
>
64
>
/** A group of hooks for a single lifecycle event. */
65
>
export interface IParsedHookGroup {
66
>
/** Canonical hook type identifier (e.g. `'SessionStart'`, `'PreToolUse'`). */
67
>
readonly type: string;
68
>
/** The commands to execute for this hook type. */
69
>
readonly commands: readonly IParsedHookCommand[];
70
>
/** URI where this hook is defined. */
71
>
readonly uri: URI;
72
>
/** Original key as it appears in the hook file. */
73
>
readonly originalId: string;
74
>
/**
75
>
* Protocol-level projection of this hook group as a child customization.
76
>
* Multiple groups parsed from the same file share the same `customization.id`
77
>
* so consumers can dedupe by id when collecting customizations.
78
>
*/
79
>
readonly customization: HookCustomization;
80
>
}
81
>
82
>
export interface IMcpServerDefinition {
83
>
readonly name: string;
84
>
readonly configuration: IMcpServerConfiguration;
85
>
readonly uri: URI;
86
>
/** Protocol-level projection of this MCP server as a child customization. */
87
>
readonly customization: McpServerCustomization;
88
>
}
89
>
90
>
/** A named resource (skill, agent, command, or instruction) within a plugin. */
91
>
export interface INamedPluginResource {
92
>
readonly uri: URI;
93
>
readonly name: string;
94
>
/**
95
>
* Optional short description, populated for resources whose readers
96
>
* parse it from the file's YAML frontmatter (e.g. agents).
97
>
*/
98
>
readonly description?: string;
99
>
}
100
>
101
>
/** A parsed agent paired with its protocol-level child customization. */
102
>
export interface IParsedAgent extends INamedPluginResource {
103
>
readonly customization: AgentCustomization;
104
>
}
105
>
106
>
/** A parsed skill paired with its protocol-level child customization. */
107
>
export interface IParsedSkill extends INamedPluginResource {
108
>
readonly customization: SkillCustomization;
109
>
}
110
>
111
>
/** A parsed rule (instruction) paired with its protocol-level child customization. */
112
>
export interface IParsedRule extends INamedPluginResource {
113
>
readonly customization: RuleCustomization;
114
>
}
115
>
116
>
/** The result of parsing a single plugin directory. */
117
>
export interface IParsedPlugin {
118
>
readonly format: PluginFormat;
119
>
readonly hooks: readonly IParsedHookGroup[];
120
>
readonly mcpServers: readonly IMcpServerDefinition[];
121
>
readonly skills: readonly IParsedSkill[];
122
>
readonly agents: readonly IParsedAgent[];
123
>
readonly instructions: readonly IParsedRule[];
124
>
}
125
>
126
>
// ---------------------------------------------------------------------------
127
>
// Plugin format detection
128
>
// ---------------------------------------------------------------------------
129
>
130
>
export const enum PluginFormat {
131
>
Copilot,
132
>
Claude,
133
>
OpenPlugin,
134
>
AgentPlugin,
135
>
}
136
>
137
>
export interface IPluginFormatConfig {
138
>
readonly format: PluginFormat;
139
>
readonly manifestPath: string;
140
>
readonly hookConfigPath: string;
141
>
readonly componentPaths?: Readonly<Partial<Record<PluginComponent, string | false>>>;
142
>
readonly requiresManifest?: boolean;
143
>
readonly pluginRootTokens: readonly string[];
144
>
readonly pluginRootEnvVars: readonly string[];
145
>
/** Parses hooks from a JSON object using the format's conventions. */
146
>
parseHooks(hookUri: URI, json: unknown, pluginUri: URI, workspaceRoot: URI | undefined, userHome: URI): IParsedHookGroup[];
147
>
}
148
>
149
>
export type PluginComponent = 'commands' | 'skills' | 'agents' | 'rules' | 'hooks' | 'mcpServers';
150
>
151
>
const COPILOT_FORMAT: IPluginFormatConfig = {
152
>
format: PluginFormat.Copilot,
153
>
manifestPath: 'plugin.json',
154
>
hookConfigPath: 'hooks.json',
155
>
pluginRootTokens: ['${PLUGIN_ROOT}', '${CLAUDE_PLUGIN_ROOT}'],
156
>
pluginRootEnvVars: ['PLUGIN_ROOT', 'CLAUDE_PLUGIN_ROOT'],
157
>
parseHooks(hookUri, json, _pluginUri, workspaceRoot, userHome) {
158
return parseHooksJson(hookUri, json, workspaceRoot, userHome);
159
},
161
>
162
>
const CLAUDE_FORMAT: IPluginFormatConfig = {
163
>
format: PluginFormat.Claude,
164
>
manifestPath: '.claude-plugin/plugin.json',
165
>
hookConfigPath: 'hooks/hooks.json',
166
>
pluginRootTokens: ['${PLUGIN_ROOT}', '${CLAUDE_PLUGIN_ROOT}'],
167
>
pluginRootEnvVars: ['PLUGIN_ROOT', 'CLAUDE_PLUGIN_ROOT'],
168
>
parseHooks(hookUri, json, pluginUri, workspaceRoot, userHome) {
169
return interpolateHookPluginRoot(hookUri, json, pluginUri, workspaceRoot, userHome, '${CLAUDE_PLUGIN_ROOT}', 'CLAUDE_PLUGIN_ROOT');
170
},
172
>
173
>
const OPEN_PLUGIN_FORMAT: IPluginFormatConfig = {
174
>
format: PluginFormat.OpenPlugin,
175
>
manifestPath: '.plugin/plugin.json',
176
>
hookConfigPath: 'hooks/hooks.json',
177
>
pluginRootTokens: ['${PLUGIN_ROOT}', '${CLAUDE_PLUGIN_ROOT}'],
178
>
pluginRootEnvVars: ['PLUGIN_ROOT', 'CLAUDE_PLUGIN_ROOT'],
179
>
parseHooks(hookUri, json, pluginUri, workspaceRoot, userHome) {
180
return interpolateHookPluginRoot(hookUri, json, pluginUri, workspaceRoot, userHome, '${PLUGIN_ROOT}', 'PLUGIN_ROOT');
181
},
183
>
184
>
const AGENT_PLUGIN_FORMAT: IPluginFormatConfig = {
185
>
format: PluginFormat.AgentPlugin,
186
>
manifestPath: 'plugin.json',
187
>
hookConfigPath: '',
188
>
componentPaths: {
189
>
commands: false,
190
>
skills: 'skills',
191
>
agents: false,
192
>
rules: false,
193
>
hooks: false,
194
>
mcpServers: 'mcp.json',
195
>
},
196
>
requiresManifest: true,
197
>
pluginRootTokens: [],
198
>
pluginRootEnvVars: [],
199
>
parseHooks() {
200
return [];
201
},
203
>
204
export async function detectPluginFormat(pluginUri: URI, fileService: IFileService): Promise<IPluginFormatConfig> {
205
if (await readAgentPluginManifest(pluginUri, fileService)) {