@agentplugins/adapter-copilot 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 +46 -0
- package/dist/index.d.mts +238 -0
- package/dist/index.d.ts +238 -0
- package/dist/index.js +618 -0
- package/dist/index.mjs +593 -0
- package/package.json +52 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,593 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import {
|
|
3
|
+
Severity
|
|
4
|
+
} from "@agentplugins/core";
|
|
5
|
+
var PLATFORM_NAME = "copilot";
|
|
6
|
+
var DISPLAY_NAME = "GitHub Copilot CLI";
|
|
7
|
+
var MANIFEST_PATH = "plugin.json";
|
|
8
|
+
var MANIFEST_FORMAT = "json";
|
|
9
|
+
var SUPPORTED_HOOKS = [
|
|
10
|
+
"sessionStart",
|
|
11
|
+
"sessionEnd",
|
|
12
|
+
"userPromptSubmit",
|
|
13
|
+
"preToolUse",
|
|
14
|
+
"postToolUse",
|
|
15
|
+
"postToolUseFailure",
|
|
16
|
+
"permissionRequest",
|
|
17
|
+
"subagentStart",
|
|
18
|
+
"subagentStop",
|
|
19
|
+
"preCompact",
|
|
20
|
+
"notification"
|
|
21
|
+
];
|
|
22
|
+
var SUPPORTED_HANDLERS = ["command", "http", "prompt"];
|
|
23
|
+
var HOOK_MAP = {
|
|
24
|
+
sessionStart: "sessionStart",
|
|
25
|
+
sessionEnd: "sessionEnd",
|
|
26
|
+
userPromptSubmit: "userPromptSubmitted",
|
|
27
|
+
preToolUse: "preToolUse",
|
|
28
|
+
postToolUse: "postToolUse",
|
|
29
|
+
postToolUseFailure: "postToolUseFailure",
|
|
30
|
+
permissionRequest: "permissionRequest",
|
|
31
|
+
subagentStart: "subagentStart",
|
|
32
|
+
subagentStop: "agentStop",
|
|
33
|
+
// Copilot has no subagentStop; agentStop is closest
|
|
34
|
+
preCompact: "preCompact",
|
|
35
|
+
notification: "notification",
|
|
36
|
+
// Explicitly unsupported hooks
|
|
37
|
+
userPromptExpansion: void 0,
|
|
38
|
+
permissionDenied: void 0,
|
|
39
|
+
postCompact: void 0,
|
|
40
|
+
stop: void 0,
|
|
41
|
+
stopFailure: void 0,
|
|
42
|
+
fileChanged: void 0,
|
|
43
|
+
cwdChanged: void 0,
|
|
44
|
+
setup: void 0
|
|
45
|
+
};
|
|
46
|
+
var FIRE_AND_FORGET_HOOKS = ["notification"];
|
|
47
|
+
var FAIL_CLOSED_HOOKS = ["preToolUse"];
|
|
48
|
+
var MAX_HOOK_TIMEOUT_SECONDS = 30;
|
|
49
|
+
var MAX_ADDITIONAL_CONTEXT_BYTES = 10 * 1024;
|
|
50
|
+
var PROMPT_ALLOWED_HOOK = "sessionStart";
|
|
51
|
+
function byteSize(value) {
|
|
52
|
+
try {
|
|
53
|
+
return new globalThis.TextEncoder().encode(JSON.stringify(value)).length;
|
|
54
|
+
} catch {
|
|
55
|
+
return 0;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function issue(severity, message, field) {
|
|
59
|
+
return { severity, message, field };
|
|
60
|
+
}
|
|
61
|
+
function checkUnsupportedHook(hook, path) {
|
|
62
|
+
const mapped = HOOK_MAP[hook];
|
|
63
|
+
if (mapped === void 0) {
|
|
64
|
+
return issue(
|
|
65
|
+
Severity.ERROR,
|
|
66
|
+
`Hook "${hook}" is not supported by the Copilot CLI platform. Supported hooks: ${SUPPORTED_HOOKS.map((h) => `"${h}"`).join(", ")}.`,
|
|
67
|
+
path
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
return void 0;
|
|
71
|
+
}
|
|
72
|
+
function validateHandler(handler, hookName, path) {
|
|
73
|
+
const issues = [];
|
|
74
|
+
const handlerPath = `${path}.handler`;
|
|
75
|
+
if (!SUPPORTED_HANDLERS.includes(handler.type)) {
|
|
76
|
+
issues.push(
|
|
77
|
+
issue(
|
|
78
|
+
Severity.ERROR,
|
|
79
|
+
`Handler type "${handler.type}" is not supported by Copilot CLI. Supported types: ${SUPPORTED_HANDLERS.map((t) => `"${t}"`).join(", ")}.`,
|
|
80
|
+
handlerPath
|
|
81
|
+
)
|
|
82
|
+
);
|
|
83
|
+
return issues;
|
|
84
|
+
}
|
|
85
|
+
switch (handler.type) {
|
|
86
|
+
case "command": {
|
|
87
|
+
const cmd = handler.config;
|
|
88
|
+
if (!cmd.shell) {
|
|
89
|
+
issues.push(
|
|
90
|
+
issue(
|
|
91
|
+
Severity.ERROR,
|
|
92
|
+
`Command handler must specify "shell" (e.g. "bash" or "powershell").`,
|
|
93
|
+
`${handlerPath}.config.shell`
|
|
94
|
+
)
|
|
95
|
+
);
|
|
96
|
+
} else if (!["bash", "powershell"].includes(cmd.shell)) {
|
|
97
|
+
issues.push(
|
|
98
|
+
issue(
|
|
99
|
+
Severity.WARNING,
|
|
100
|
+
`Shell "${cmd.shell}" may not be supported by all Copilot CLI environments. Recommended: "bash" or "powershell".`,
|
|
101
|
+
`${handlerPath}.config.shell`
|
|
102
|
+
)
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
if (!cmd.script && !cmd.command) {
|
|
106
|
+
issues.push(
|
|
107
|
+
issue(
|
|
108
|
+
Severity.ERROR,
|
|
109
|
+
`Command handler must specify "script" or "command".`,
|
|
110
|
+
`${handlerPath}.config`
|
|
111
|
+
)
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
case "http": {
|
|
117
|
+
const httpCfg = handler.config;
|
|
118
|
+
if (!httpCfg.url) {
|
|
119
|
+
issues.push(
|
|
120
|
+
issue(
|
|
121
|
+
Severity.ERROR,
|
|
122
|
+
`HTTP handler must specify "url".`,
|
|
123
|
+
`${handlerPath}.config.url`
|
|
124
|
+
)
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
if (httpCfg.method && httpCfg.method.toUpperCase() !== "POST") {
|
|
128
|
+
issues.push(
|
|
129
|
+
issue(
|
|
130
|
+
Severity.ERROR,
|
|
131
|
+
`Copilot CLI only supports POST for HTTP handlers. Found method: "${httpCfg.method}".`,
|
|
132
|
+
`${handlerPath}.config.method`
|
|
133
|
+
)
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
case "prompt": {
|
|
139
|
+
if (hookName !== PROMPT_ALLOWED_HOOK) {
|
|
140
|
+
issues.push(
|
|
141
|
+
issue(
|
|
142
|
+
Severity.ERROR,
|
|
143
|
+
`Prompt handler type is only allowed for the "${PROMPT_ALLOWED_HOOK}" hook. Hook "${hookName}" cannot use a prompt handler.`,
|
|
144
|
+
handlerPath
|
|
145
|
+
)
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
const promptCfg = handler.config;
|
|
149
|
+
if (!promptCfg.template) {
|
|
150
|
+
issues.push(
|
|
151
|
+
issue(
|
|
152
|
+
Severity.ERROR,
|
|
153
|
+
`Prompt handler must specify a "template".`,
|
|
154
|
+
`${handlerPath}.config.template`
|
|
155
|
+
)
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return issues;
|
|
162
|
+
}
|
|
163
|
+
function validateHookConstraints(hook, hookName, path) {
|
|
164
|
+
const issues = [];
|
|
165
|
+
const copilotHook = hook;
|
|
166
|
+
if (copilotHook.additionalContext) {
|
|
167
|
+
const size = byteSize(copilotHook.additionalContext);
|
|
168
|
+
if (size > MAX_ADDITIONAL_CONTEXT_BYTES) {
|
|
169
|
+
issues.push(
|
|
170
|
+
issue(
|
|
171
|
+
Severity.ERROR,
|
|
172
|
+
`additionalContext exceeds ${MAX_ADDITIONAL_CONTEXT_BYTES} bytes (${size} bytes found). Reduce the size of data passed to hooks.`,
|
|
173
|
+
`${path}.additionalContext`
|
|
174
|
+
)
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (copilotHook.timeout !== void 0 && copilotHook.timeout > MAX_HOOK_TIMEOUT_SECONDS) {
|
|
179
|
+
issues.push(
|
|
180
|
+
issue(
|
|
181
|
+
Severity.ERROR,
|
|
182
|
+
`Hook timeout (${copilotHook.timeout}s) exceeds the Copilot CLI maximum of ${MAX_HOOK_TIMEOUT_SECONDS}s.`,
|
|
183
|
+
`${path}.timeout`
|
|
184
|
+
)
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
if (hookName === "preToolUse") {
|
|
188
|
+
const cfg = hook.handler?.config;
|
|
189
|
+
if (!cfg?.matcher?.field || !cfg?.matcher?.value) {
|
|
190
|
+
issues.push(
|
|
191
|
+
issue(
|
|
192
|
+
Severity.WARNING,
|
|
193
|
+
`preToolUse hooks should specify a matcher (e.g. toolName: "Bash") to avoid intercepting every tool call. Without a matcher, the hook runs for ALL tools and errors will deny them (fail-closed behavior).`,
|
|
194
|
+
`${path}.handler.config.matcher`
|
|
195
|
+
)
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return issues;
|
|
200
|
+
}
|
|
201
|
+
function compileSkill(skill) {
|
|
202
|
+
const copilotSkill = skill;
|
|
203
|
+
const descriptor = {
|
|
204
|
+
name: skill.name,
|
|
205
|
+
description: skill.description,
|
|
206
|
+
parameters: copilotSkill.parameters?.map((p) => ({
|
|
207
|
+
name: p.name,
|
|
208
|
+
type: p.type,
|
|
209
|
+
description: p.description,
|
|
210
|
+
required: p.required
|
|
211
|
+
})),
|
|
212
|
+
examples: copilotSkill.examples
|
|
213
|
+
};
|
|
214
|
+
const skillDir = `skills/${skill.name}`;
|
|
215
|
+
let md = `# ${skill.name}
|
|
216
|
+
|
|
217
|
+
`;
|
|
218
|
+
md += `${skill.description}
|
|
219
|
+
|
|
220
|
+
`;
|
|
221
|
+
if (descriptor.parameters && descriptor.parameters.length > 0) {
|
|
222
|
+
md += `## Parameters
|
|
223
|
+
|
|
224
|
+
`;
|
|
225
|
+
md += `| Name | Type | Required | Description |
|
|
226
|
+
`;
|
|
227
|
+
md += `|------|------|----------|-------------|
|
|
228
|
+
`;
|
|
229
|
+
for (const p of descriptor.parameters) {
|
|
230
|
+
md += `| ${p.name} | ${p.type} | ${p.required ? "Yes" : "No"} | ${p.description ?? ""} |
|
|
231
|
+
`;
|
|
232
|
+
}
|
|
233
|
+
md += `
|
|
234
|
+
`;
|
|
235
|
+
}
|
|
236
|
+
if (descriptor.examples && descriptor.examples.length > 0) {
|
|
237
|
+
md += `## Examples
|
|
238
|
+
|
|
239
|
+
`;
|
|
240
|
+
for (const ex of descriptor.examples) {
|
|
241
|
+
md += `\`\`\`
|
|
242
|
+
${ex}
|
|
243
|
+
\`\`\`
|
|
244
|
+
|
|
245
|
+
`;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
md += `## Metadata
|
|
249
|
+
|
|
250
|
+
`;
|
|
251
|
+
md += `\`\`\`json
|
|
252
|
+
${JSON.stringify(descriptor, null, 2)}
|
|
253
|
+
\`\`\`
|
|
254
|
+
`;
|
|
255
|
+
return { descriptor, skillDir, skillMdContent: md };
|
|
256
|
+
}
|
|
257
|
+
function compileHookEntry(hookName, hook) {
|
|
258
|
+
const handler = hook.handler;
|
|
259
|
+
const copilotEvent = HOOK_MAP[hookName];
|
|
260
|
+
const entry = {
|
|
261
|
+
event: copilotEvent,
|
|
262
|
+
type: handler.type,
|
|
263
|
+
handler: ""
|
|
264
|
+
};
|
|
265
|
+
switch (handler.type) {
|
|
266
|
+
case "command": {
|
|
267
|
+
const cfg = handler.config;
|
|
268
|
+
entry.handler = cfg.script ?? cfg.command ?? "";
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
271
|
+
case "http": {
|
|
272
|
+
const cfg = handler.config;
|
|
273
|
+
entry.handler = cfg.url;
|
|
274
|
+
break;
|
|
275
|
+
}
|
|
276
|
+
case "prompt": {
|
|
277
|
+
const cfg = handler.config;
|
|
278
|
+
entry.handler = cfg.template;
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
const matcher = handler.config?.matcher;
|
|
283
|
+
if (matcher?.field && matcher?.value) {
|
|
284
|
+
entry.matcher = {
|
|
285
|
+
field: matcher.field,
|
|
286
|
+
value: matcher.value
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
if (FIRE_AND_FORGET_HOOKS.includes(copilotEvent)) {
|
|
290
|
+
entry.awaitResponse = false;
|
|
291
|
+
}
|
|
292
|
+
const copilotHook = hook;
|
|
293
|
+
const timeout = copilotHook.timeout ?? MAX_HOOK_TIMEOUT_SECONDS;
|
|
294
|
+
entry.timeout = Math.min(timeout, MAX_HOOK_TIMEOUT_SECONDS);
|
|
295
|
+
if (FAIL_CLOSED_HOOKS.includes(copilotEvent)) {
|
|
296
|
+
entry.failClosed = true;
|
|
297
|
+
}
|
|
298
|
+
return entry;
|
|
299
|
+
}
|
|
300
|
+
var CopilotAdapter = class {
|
|
301
|
+
/** Platform identifier. */
|
|
302
|
+
name = PLATFORM_NAME;
|
|
303
|
+
/** Human-readable display name. */
|
|
304
|
+
displayName = DISPLAY_NAME;
|
|
305
|
+
/** Universal hooks supported by this adapter. */
|
|
306
|
+
supportedHooks = SUPPORTED_HOOKS;
|
|
307
|
+
/** Handler types understood by Copilot CLI. */
|
|
308
|
+
supportedHandlers = SUPPORTED_HANDLERS;
|
|
309
|
+
/** Path to the generated manifest file. */
|
|
310
|
+
manifestPath = MANIFEST_PATH;
|
|
311
|
+
/** Manifest format (JSON). */
|
|
312
|
+
manifestFormat = MANIFEST_FORMAT;
|
|
313
|
+
/**
|
|
314
|
+
* Validate a {@link PluginManifest} for Copilot CLI compatibility.
|
|
315
|
+
*
|
|
316
|
+
* Checks performed:
|
|
317
|
+
* 1. Only supported hooks are referenced.
|
|
318
|
+
* 2. Handler types are within the supported set.
|
|
319
|
+
* 3. Command handlers specify shell (bash/powershell) and script/command.
|
|
320
|
+
* 4. HTTP handlers use POST (the only method Copilot sends).
|
|
321
|
+
* 5. Prompt handlers are only used on `sessionStart`.
|
|
322
|
+
* 6. additionalContext does not exceed 10 KB.
|
|
323
|
+
* 7. Hook timeouts do not exceed 30 seconds.
|
|
324
|
+
* 8. preToolUse hooks are encouraged to specify a matcher.
|
|
325
|
+
* 9. All referenced skills have required fields (name, description).
|
|
326
|
+
*
|
|
327
|
+
* @param plugin – the plugin manifest to validate
|
|
328
|
+
* @returns array of validation issues (empty if fully valid)
|
|
329
|
+
*/
|
|
330
|
+
validate(plugin) {
|
|
331
|
+
const issues = [];
|
|
332
|
+
if (plugin.hooks) {
|
|
333
|
+
for (const [hookName, hookDef] of Object.entries(plugin.hooks)) {
|
|
334
|
+
const hookPath = `hooks.${hookName}`;
|
|
335
|
+
const uHook = hookName;
|
|
336
|
+
const unsupported = checkUnsupportedHook(uHook, hookPath);
|
|
337
|
+
if (unsupported) {
|
|
338
|
+
issues.push(unsupported);
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
if (hookDef.handler) {
|
|
342
|
+
issues.push(
|
|
343
|
+
...validateHandler(
|
|
344
|
+
hookDef.handler,
|
|
345
|
+
uHook,
|
|
346
|
+
hookPath
|
|
347
|
+
)
|
|
348
|
+
);
|
|
349
|
+
} else {
|
|
350
|
+
issues.push(
|
|
351
|
+
issue(
|
|
352
|
+
Severity.ERROR,
|
|
353
|
+
`Hook "${hookName}" is missing a handler definition.`,
|
|
354
|
+
hookPath
|
|
355
|
+
)
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
issues.push(...validateHookConstraints(hookDef, uHook, hookPath));
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
if (plugin.skills) {
|
|
362
|
+
for (let i = 0; i < plugin.skills.length; i++) {
|
|
363
|
+
const skill = plugin.skills[i];
|
|
364
|
+
const skillPath = `skills[${i}]`;
|
|
365
|
+
if (!skill.name || skill.name.trim() === "") {
|
|
366
|
+
issues.push(
|
|
367
|
+
issue(
|
|
368
|
+
Severity.ERROR,
|
|
369
|
+
`Skill at index ${i} is missing a "name".`,
|
|
370
|
+
`${skillPath}.name`
|
|
371
|
+
)
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
if (!skill.description || skill.description.trim() === "") {
|
|
375
|
+
issues.push(
|
|
376
|
+
issue(
|
|
377
|
+
Severity.ERROR,
|
|
378
|
+
`Skill "${skill.name ?? `#${i}`}" is missing a "description".`,
|
|
379
|
+
`${skillPath}.description`
|
|
380
|
+
)
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
const copilotSkill = skill;
|
|
384
|
+
if (copilotSkill.parameters) {
|
|
385
|
+
const validTypes = ["string", "number", "boolean", "object", "array"];
|
|
386
|
+
for (let j = 0; j < copilotSkill.parameters.length; j++) {
|
|
387
|
+
const p = copilotSkill.parameters[j];
|
|
388
|
+
if (!validTypes.includes(p.type)) {
|
|
389
|
+
issues.push(
|
|
390
|
+
issue(
|
|
391
|
+
Severity.WARNING,
|
|
392
|
+
`Parameter "${p.name}" has type "${p.type}" which may not be recognised by Copilot CLI. Valid types: ${validTypes.join(", ")}.`,
|
|
393
|
+
`${skillPath}.parameters[${j}].type`
|
|
394
|
+
)
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
if (plugin.hooks?.preToolUse) {
|
|
402
|
+
const preToolHandler = plugin.hooks.preToolUse.handler;
|
|
403
|
+
const matcher = preToolHandler?.config?.matcher;
|
|
404
|
+
if (!matcher) {
|
|
405
|
+
issues.push(
|
|
406
|
+
issue(
|
|
407
|
+
Severity.WARNING,
|
|
408
|
+
`Plugin defines a "preToolUse" hook without a matcher. This will intercept ALL tool calls and any error will deny them (fail-closed). Consider adding a matcher like { field: "toolName", value: "Bash" }.`,
|
|
409
|
+
`hooks.preToolUse.handler.config.matcher`
|
|
410
|
+
)
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
return issues;
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Compile a {@link PluginManifest} into the Copilot CLI file layout.
|
|
418
|
+
*
|
|
419
|
+
* Produces:
|
|
420
|
+
* - `plugin.json` – top-level manifest with metadata and file references
|
|
421
|
+
* - `hooks.json` – hook bindings mapped to Copilot event names
|
|
422
|
+
* - `skills/<name>/SKILL.md` – one file per skill
|
|
423
|
+
* - `.mcp.json` – MCP server configuration (if MCP servers are defined)
|
|
424
|
+
*
|
|
425
|
+
* @param plugin – the validated plugin manifest
|
|
426
|
+
* @returns {@link AdapterOutput} containing all generated files
|
|
427
|
+
*/
|
|
428
|
+
compile(plugin) {
|
|
429
|
+
const files = [];
|
|
430
|
+
const warnings = [];
|
|
431
|
+
const hookEntries = [];
|
|
432
|
+
if (plugin.hooks) {
|
|
433
|
+
for (const [hookName, hookDef] of Object.entries(plugin.hooks)) {
|
|
434
|
+
const uHook = hookName;
|
|
435
|
+
if (!SUPPORTED_HOOKS.includes(uHook)) {
|
|
436
|
+
warnings.push(
|
|
437
|
+
`Skipping unsupported hook "${hookName}" during compilation.`
|
|
438
|
+
);
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
const handler = hookDef.handler;
|
|
442
|
+
if (handler.type === "prompt") {
|
|
443
|
+
const entry = compileHookEntry(uHook, hookDef);
|
|
444
|
+
hookEntries.push(entry);
|
|
445
|
+
} else {
|
|
446
|
+
const entry = compileHookEntry(uHook, hookDef);
|
|
447
|
+
if (handler.type === "command") {
|
|
448
|
+
const cfg = handler.config;
|
|
449
|
+
const scriptName = `hook-${hookName}.${cfg.shell === "powershell" ? "ps1" : "sh"}`;
|
|
450
|
+
const scriptContent = generateWrapperScript(cfg);
|
|
451
|
+
files.push({
|
|
452
|
+
path: scriptName,
|
|
453
|
+
content: scriptContent
|
|
454
|
+
});
|
|
455
|
+
entry.handler = `./${scriptName}`;
|
|
456
|
+
}
|
|
457
|
+
hookEntries.push(entry);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
if (hookEntries.length > 0) {
|
|
462
|
+
files.push({
|
|
463
|
+
path: "hooks.json",
|
|
464
|
+
content: JSON.stringify(hookEntries, null, 2)
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
const skillDirs = [];
|
|
468
|
+
if (plugin.skills && plugin.skills.length > 0) {
|
|
469
|
+
for (const skill of plugin.skills) {
|
|
470
|
+
const { skillDir, skillMdContent } = compileSkill(skill);
|
|
471
|
+
skillDirs.push(skillDir);
|
|
472
|
+
files.push({
|
|
473
|
+
path: `${skillDir}/SKILL.md`,
|
|
474
|
+
content: skillMdContent
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
const mcpConfig = this.buildMcpConfig(plugin);
|
|
479
|
+
if (mcpConfig) {
|
|
480
|
+
files.push({
|
|
481
|
+
path: ".mcp.json",
|
|
482
|
+
content: JSON.stringify(mcpConfig, null, 2)
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
const copilotPlugin = plugin;
|
|
486
|
+
const copilotManifest = {
|
|
487
|
+
schemaVersion: "1.0",
|
|
488
|
+
id: copilotPlugin.id ?? copilotPlugin.name,
|
|
489
|
+
name: copilotPlugin.name,
|
|
490
|
+
description: copilotPlugin.description,
|
|
491
|
+
version: copilotPlugin.version,
|
|
492
|
+
strict: copilotPlugin.strict ?? true,
|
|
493
|
+
maxAdditionalContextBytes: MAX_ADDITIONAL_CONTEXT_BYTES,
|
|
494
|
+
hookTimeoutSeconds: MAX_HOOK_TIMEOUT_SECONDS,
|
|
495
|
+
author: typeof copilotPlugin.author === "string" ? copilotPlugin.author : copilotPlugin.author?.name,
|
|
496
|
+
homepage: copilotPlugin.homepage,
|
|
497
|
+
license: copilotPlugin.license,
|
|
498
|
+
tags: copilotPlugin.tags ?? copilotPlugin.keywords ?? void 0,
|
|
499
|
+
...hookEntries.length > 0 && { hooks: "hooks.json" },
|
|
500
|
+
...skillDirs.length > 0 && { skills: skillDirs },
|
|
501
|
+
...mcpConfig && { mcp: ".mcp.json" }
|
|
502
|
+
};
|
|
503
|
+
files.unshift({
|
|
504
|
+
path: MANIFEST_PATH,
|
|
505
|
+
content: JSON.stringify(copilotManifest, null, 2)
|
|
506
|
+
});
|
|
507
|
+
return {
|
|
508
|
+
files,
|
|
509
|
+
manifest: copilotManifest,
|
|
510
|
+
warnings: warnings.length > 0 ? warnings : [],
|
|
511
|
+
issues: []
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Build an MCP server configuration object from the plugin manifest.
|
|
516
|
+
*
|
|
517
|
+
* If the plugin defines MCP servers (in `plugin.mcpServers`), they are
|
|
518
|
+
* converted into Copilot's `.mcp.json` format.
|
|
519
|
+
*
|
|
520
|
+
* @param plugin – the plugin manifest
|
|
521
|
+
* @returns MCP config object, or `null` if no MCP servers are defined
|
|
522
|
+
*/
|
|
523
|
+
buildMcpConfig(plugin) {
|
|
524
|
+
const servers = plugin.mcpServers;
|
|
525
|
+
if (!servers || servers.length === 0) {
|
|
526
|
+
return null;
|
|
527
|
+
}
|
|
528
|
+
return {
|
|
529
|
+
schemaVersion: "1.0",
|
|
530
|
+
servers: servers.map((s) => ({
|
|
531
|
+
id: s.id,
|
|
532
|
+
name: s.name,
|
|
533
|
+
transport: s.transport,
|
|
534
|
+
...s.command && { command: s.command },
|
|
535
|
+
...s.args && { args: s.args },
|
|
536
|
+
...s.url && { url: s.url },
|
|
537
|
+
...s.env && { env: s.env }
|
|
538
|
+
}))
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
};
|
|
542
|
+
function generateWrapperScript(cfg) {
|
|
543
|
+
const scriptBody = cfg.script ?? cfg.command ?? "";
|
|
544
|
+
if (cfg.shell === "powershell") {
|
|
545
|
+
return `
|
|
546
|
+
# Auto-generated Copilot CLI hook wrapper
|
|
547
|
+
param(
|
|
548
|
+
[Parameter(ValueFromPipeline = $true)]
|
|
549
|
+
[string]$Payload
|
|
550
|
+
)
|
|
551
|
+
|
|
552
|
+
# Read payload from stdin if not provided as argument
|
|
553
|
+
if (-not $Payload) {
|
|
554
|
+
$Payload = $input | Out-String
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
# Parse JSON payload
|
|
558
|
+
$HookData = $Payload | ConvertFrom-Json -ErrorAction SilentlyContinue
|
|
559
|
+
|
|
560
|
+
# TODO: export relevant fields as environment variables if needed
|
|
561
|
+
# $env:COPILOT_TOOL_NAME = $HookData.toolName
|
|
562
|
+
|
|
563
|
+
# Execute user script
|
|
564
|
+
${scriptBody}
|
|
565
|
+
`.trim();
|
|
566
|
+
}
|
|
567
|
+
return `#!/usr/bin/env bash
|
|
568
|
+
# Auto-generated Copilot CLI hook wrapper
|
|
569
|
+
|
|
570
|
+
set -euo pipefail
|
|
571
|
+
|
|
572
|
+
# Read JSON payload from stdin
|
|
573
|
+
PAYLOAD="$(cat)"
|
|
574
|
+
|
|
575
|
+
# Parse payload with jq if available; otherwise pass through raw
|
|
576
|
+
if command -v jq &> /dev/null; then
|
|
577
|
+
TOOL_NAME="$(echo "$PAYLOAD" | jq -r '.toolName // empty')"
|
|
578
|
+
export COPILOT_TOOL_NAME="\${TOOL_NAME:-}"
|
|
579
|
+
fi
|
|
580
|
+
|
|
581
|
+
# Execute user script
|
|
582
|
+
${scriptBody}
|
|
583
|
+
`.trim();
|
|
584
|
+
}
|
|
585
|
+
var copilotAdapter = new CopilotAdapter();
|
|
586
|
+
function createCopilotAdapter() {
|
|
587
|
+
return new CopilotAdapter();
|
|
588
|
+
}
|
|
589
|
+
export {
|
|
590
|
+
CopilotAdapter,
|
|
591
|
+
copilotAdapter,
|
|
592
|
+
createCopilotAdapter
|
|
593
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@agentplugins/adapter-copilot",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "GitHub Copilot CLI platform adapter for AgentPlugins — compiles AgentPlugins plugins into Copilot-compatible manifests, hooks, skills, and MCP configuration",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"import": "./dist/index.mjs",
|
|
10
|
+
"require": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"access": "public"
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsup src/index.ts --format cjs,esm --dts",
|
|
23
|
+
"typecheck": "tsc --noEmit",
|
|
24
|
+
"lint": "eslint src/**/*.ts",
|
|
25
|
+
"test": "vitest run"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@agentplugins/core": "workspace:*"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"tsup": "^8.0.0",
|
|
32
|
+
"typescript": "^5.3.0",
|
|
33
|
+
"vitest": "^1.0.0"
|
|
34
|
+
},
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=18.0.0"
|
|
37
|
+
},
|
|
38
|
+
"license": "MIT",
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "https://github.com/espetro/agentplugins.git",
|
|
42
|
+
"directory": "packages/adapter-copilot"
|
|
43
|
+
},
|
|
44
|
+
"keywords": [
|
|
45
|
+
"agentplugins",
|
|
46
|
+
"github-copilot",
|
|
47
|
+
"copilot-cli",
|
|
48
|
+
"plugin-adapter",
|
|
49
|
+
"ai-agent",
|
|
50
|
+
"mcp"
|
|
51
|
+
]
|
|
52
|
+
}
|