portable-agent-layer 0.24.2 → 0.25.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 CHANGED
@@ -101,6 +101,7 @@ pal cli install # all available (default)
101
101
  | Claude Code | Full | Yes | Yes | Yes | Yes |
102
102
  | opencode | Full | Yes | Yes (plugin) | Yes | Yes |
103
103
  | Cursor | Full | Yes | Yes | Yes (injected via hook) | Yes |
104
+ | GitHub Copilot | Full | Yes | Yes | Yes (via copilot-instructions.md) | Yes |
104
105
  | Codex | Partial | Yes | No | Yes | No |
105
106
 
106
107
  ---
@@ -125,6 +126,7 @@ pal cli install # all available (default)
125
126
  | `PAL_CLAUDE_DIR` | Override Claude config dir (default: `~/.claude`) |
126
127
  | `PAL_OPENCODE_DIR` | Override opencode config dir (default: `~/.config/opencode`) |
127
128
  | `PAL_CURSOR_DIR` | Override Cursor config dir (default: `~/.cursor`) |
129
+ | `PAL_COPILOT_DIR` | Override Copilot config dir (default: `~/.copilot`) |
128
130
  | `PAL_CODEX_DIR` | Override Codex config dir (default: `~/.codex`) |
129
131
  | `PAL_AGENTS_DIR` | Override agents dir (default: `~/.agents`) |
130
132
 
@@ -0,0 +1,33 @@
1
+ {
2
+ "version": 1,
3
+ "hooks": {
4
+ "sessionStart": [
5
+ {
6
+ "type": "command",
7
+ "bash": "PAL_AGENT=copilot bun run {{PKG_ROOT}}/src/hooks/LoadContext.ts"
8
+ }
9
+ ],
10
+ "userPromptSubmitted": [
11
+ {
12
+ "type": "command",
13
+ "bash": "bun run {{PKG_ROOT}}/src/hooks/UserPromptOrchestrator.ts"
14
+ }
15
+ ],
16
+ "preToolUse": [
17
+ {
18
+ "type": "command",
19
+ "bash": "bun run {{PKG_ROOT}}/src/hooks/SecurityValidator.ts"
20
+ },
21
+ {
22
+ "type": "command",
23
+ "bash": "bun run {{PKG_ROOT}}/src/hooks/SkillGuard.ts"
24
+ }
25
+ ],
26
+ "agentStop": [
27
+ {
28
+ "type": "command",
29
+ "bash": "bun run {{PKG_ROOT}}/src/hooks/StopOrchestrator.ts"
30
+ }
31
+ ]
32
+ }
33
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "portable-agent-layer",
3
- "version": "0.24.2",
3
+ "version": "0.25.0",
4
4
  "description": "PAL — Portable Agent Layer: persistent personal context for AI coding assistants",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli/index.ts CHANGED
@@ -212,46 +212,49 @@ function showHelp() {
212
212
  PAL_CLAUDE_DIR Override Claude config dir (default: ~/.claude)
213
213
  PAL_OPENCODE_DIR Override opencode config dir (default: ~/.config/opencode)
214
214
  PAL_CURSOR_DIR Override Cursor config dir (default: ~/.cursor)
215
+ PAL_COPILOT_DIR Override Copilot config dir (default: ~/.copilot)
215
216
  PAL_AGENTS_DIR Override agents dir (default: ~/.agents)
216
217
  `);
217
218
  }
218
219
 
219
- function parseTargets(args: string[]): {
220
- claude: boolean;
221
- opencode: boolean;
222
- cursor: boolean;
223
- } {
220
+ type Targets = { claude: boolean; opencode: boolean; cursor: boolean; copilot: boolean };
221
+
222
+ function parseTargets(args: string[]): Targets {
224
223
  let claude = false;
225
224
  let opencode = false;
226
225
  let cursor = false;
226
+ let copilot = false;
227
227
  for (const arg of args) {
228
228
  if (arg === "--claude") claude = true;
229
229
  else if (arg === "--opencode") opencode = true;
230
230
  else if (arg === "--cursor") cursor = true;
231
+ else if (arg === "--copilot") copilot = true;
231
232
  else if (arg === "--all") {
232
233
  claude = true;
233
234
  opencode = true;
234
235
  cursor = true;
236
+ copilot = true;
235
237
  }
236
238
  }
237
- if (!claude && !opencode && !cursor)
238
- return { claude: true, opencode: true, cursor: true };
239
- return { claude, opencode, cursor };
239
+ if (!claude && !opencode && !cursor && !copilot)
240
+ return { claude: true, opencode: true, cursor: true, copilot: true };
241
+ return { claude, opencode, cursor, copilot };
240
242
  }
241
243
 
242
244
  /** Resolve targets against available agents. Errors if explicitly requested but missing. */
243
- function resolveTargets(
244
- args: string[],
245
- health?: DoctorResult
246
- ): { claude: boolean; opencode: boolean; cursor: boolean } {
245
+ function resolveTargets(args: string[], health?: DoctorResult): Targets {
247
246
  const requested = parseTargets(args);
248
247
  const h = health || doctor(true);
249
248
  const explicit = args.some(
250
- (a) => a === "--claude" || a === "--opencode" || a === "--cursor" || a === "--all"
249
+ (a) =>
250
+ a === "--claude" ||
251
+ a === "--opencode" ||
252
+ a === "--cursor" ||
253
+ a === "--copilot" ||
254
+ a === "--all"
251
255
  );
252
256
 
253
257
  if (explicit) {
254
- // User explicitly requested — error if not available
255
258
  if (requested.claude && !h.claude.available) {
256
259
  log.error("Claude Code is not installed. Run 'pal cli doctor' for details.");
257
260
  process.exit(1);
@@ -264,19 +267,25 @@ function resolveTargets(
264
267
  log.error("Cursor is not installed. Run 'pal cli doctor' for details.");
265
268
  process.exit(1);
266
269
  }
270
+ if (requested.copilot && !h.copilot.available) {
271
+ log.error("Copilot is not installed. Run 'pal cli doctor' for details.");
272
+ process.exit(1);
273
+ }
267
274
  return requested;
268
275
  }
269
276
 
270
277
  // Default (no flags) — install for available agents only
271
- const targets = {
278
+ const targets: Targets = {
272
279
  claude: h.claude.available,
273
280
  opencode: h.opencode.available,
274
281
  cursor: h.cursor.available,
282
+ copilot: h.copilot.available,
275
283
  };
276
284
 
277
285
  if (!targets.claude) log.info("Skipping Claude Code (not installed)");
278
286
  if (!targets.opencode) log.info("Skipping opencode (not installed)");
279
287
  if (!targets.cursor) log.info("Skipping Cursor (not installed)");
288
+ if (!targets.copilot) log.info("Skipping Copilot (not installed)");
280
289
 
281
290
  return targets;
282
291
  }
@@ -320,6 +329,14 @@ function checkOpencodePluginInstalled(): boolean {
320
329
  return existsSync(resolve(platform.opencodeDir(), "plugins", "pal-plugin.ts"));
321
330
  }
322
331
 
332
+ function checkCopilotHooksRegistered(): boolean {
333
+ return existsSync(resolve(platform.copilotDir(), "hooks", "pal-hooks.json"));
334
+ }
335
+
336
+ function checkCopilotInstructionsPresent(): boolean {
337
+ return existsSync(resolve(platform.copilotDir(), "copilot-instructions.md"));
338
+ }
339
+
323
340
  function checkHookHealth(home: string): HookHealth {
324
341
  const logPath = resolve(home, "memory", "state", "debug.log");
325
342
 
@@ -357,6 +374,7 @@ interface DoctorResult {
357
374
  claude: ToolCheck;
358
375
  opencode: ToolCheck;
359
376
  cursor: ToolCheck;
377
+ copilot: ToolCheck;
360
378
  hasAgent: boolean;
361
379
  }
362
380
 
@@ -368,6 +386,7 @@ function doctor(silent = false): DoctorResult {
368
386
  claude: { name: "claude", available: true },
369
387
  opencode: { name: "opencode", available: true },
370
388
  cursor: { name: "cursor", available: true },
389
+ copilot: { name: "copilot", available: true },
371
390
  hasAgent: true,
372
391
  };
373
392
  }
@@ -376,7 +395,9 @@ function doctor(silent = false): DoctorResult {
376
395
  const claude = checkTool("claude");
377
396
  const opencode = checkTool("opencode");
378
397
  const cursor = checkTool("cursor");
379
- const hasAgent = claude.available || opencode.available || cursor.available;
398
+ const copilot = checkTool("copilot", ["version"]);
399
+ const hasAgent =
400
+ claude.available || opencode.available || cursor.available || copilot.available;
380
401
 
381
402
  const home = palHome();
382
403
  const telosCount = (() => {
@@ -404,6 +425,9 @@ function doctor(silent = false): DoctorResult {
404
425
  cursor.available
405
426
  ? ok(`Cursor ${cursor.version || ""}`.trim())
406
427
  : fail("Cursor — not found");
428
+ copilot.available
429
+ ? ok(`Copilot ${copilot.version || ""}`.trim())
430
+ : fail("Copilot — not found");
407
431
  ok(`PAL home: ${home}`);
408
432
  telosCount > 0 ? ok(`TELOS: ${telosCount} files`) : fail("TELOS: not scaffolded");
409
433
 
@@ -474,6 +498,12 @@ function doctor(silent = false): DoctorResult {
474
498
  ? ok(`Cursor skills: ${n}`)
475
499
  : warn("Cursor skills — none found (run 'pal cli install --cursor')");
476
500
  }
501
+ if (copilot.available) {
502
+ const n = countSkillsIn(resolve(platform.copilotDir(), "skills"));
503
+ n > 0
504
+ ? ok(`Copilot skills: ${n}`)
505
+ : warn("Copilot skills — none found (run 'pal cli install --copilot')");
506
+ }
477
507
 
478
508
  // Dependencies
479
509
  const nodeModulesPath = resolve(palPkg(), "node_modules");
@@ -497,6 +527,14 @@ function doctor(silent = false): DoctorResult {
497
527
  ? ok("Cursor hooks registered")
498
528
  : fail("Cursor hooks — not registered (run 'pal cli install --cursor')");
499
529
  }
530
+ if (copilot.available) {
531
+ checkCopilotHooksRegistered()
532
+ ? ok("Copilot hooks registered")
533
+ : fail("Copilot hooks — not registered (run 'pal cli install --copilot')");
534
+ checkCopilotInstructionsPresent()
535
+ ? ok("copilot-instructions.md present")
536
+ : warn("copilot-instructions.md missing (run 'pal cli install --copilot')");
537
+ }
500
538
 
501
539
  // API key checks
502
540
  process.env.PAL_ANTHROPIC_API_KEY
@@ -530,7 +568,7 @@ function doctor(silent = false): DoctorResult {
530
568
  console.log("");
531
569
  }
532
570
 
533
- return { bun, claude, opencode, cursor, hasAgent };
571
+ return { bun, claude, opencode, cursor, copilot, hasAgent };
534
572
  }
535
573
 
536
574
  // ── Commands ──
@@ -566,7 +604,7 @@ async function init(args: string[]) {
566
604
  }
567
605
  }
568
606
 
569
- async function install(targets: { claude: boolean; opencode: boolean; cursor: boolean }) {
607
+ async function install(targets: Targets) {
570
608
  // Ensure dependencies are installed
571
609
  const pkg = palPkg();
572
610
  log.info("Installing dependencies...");
@@ -604,6 +642,12 @@ async function install(targets: { claude: boolean; opencode: boolean; cursor: bo
604
642
  console.log("");
605
643
  }
606
644
 
645
+ if (targets.copilot) {
646
+ console.log("━━━ Copilot ━━━");
647
+ await import("../targets/copilot/install");
648
+ console.log("");
649
+ }
650
+
607
651
  log.success("Done. Existing config was preserved — only new entries were added.");
608
652
  }
609
653
 
@@ -628,6 +672,12 @@ async function uninstall(args: string[]) {
628
672
  console.log("");
629
673
  }
630
674
 
675
+ if (targets.copilot) {
676
+ console.log("━━━ Copilot ━━━");
677
+ await import("../targets/copilot/uninstall");
678
+ console.log("");
679
+ }
680
+
631
681
  log.success(
632
682
  `PAL uninstalled. Your TELOS, skills, and memory are still in ${palHome()}.`
633
683
  );
@@ -4,11 +4,17 @@
4
4
  * Static context (TELOS, setup prompt) is loaded natively from AGENTS.md / CLAUDE.md.
5
5
  * This hook injects dynamic context only: wisdom principles, relationship notes,
6
6
  * learning digest, signal trends, failure patterns, active work state.
7
+ *
8
+ * Copilot: sessionStart output is ignored by the runtime. Instead, we write the merged
9
+ * context directly to ~/.copilot/copilot-instructions.md so it is picked up on load.
7
10
  */
8
11
 
12
+ import { existsSync, lstatSync, unlinkSync, writeFileSync } from "node:fs";
13
+ import { resolve } from "node:path";
9
14
  import { buildClaudeMd, regenerateIfNeeded } from "./lib/claude-md";
10
15
  import { buildSystemReminder } from "./lib/context";
11
16
  import { logDebug, logError } from "./lib/log";
17
+ import { platform } from "./lib/paths";
12
18
 
13
19
  // --- Skip heavy context for subagents ---
14
20
  const isSubagent =
@@ -28,21 +34,32 @@ try {
28
34
  logError("LoadContext:regenerate", err);
29
35
  }
30
36
 
31
- // --- Context to stdout ---
37
+ // --- Context to stdout (or file for Copilot) ---
32
38
  try {
33
39
  const reminder = buildSystemReminder();
34
40
  if (!reminder) process.exit(0);
35
41
 
36
- if (process.env.CURSOR_VERSION) {
42
+ if (process.env.PAL_AGENT === "copilot") {
43
+ // Copilot: sessionStart output is ignored — write merged context to copilot-instructions.md
44
+ const instructionsPath = resolve(platform.copilotDir(), "copilot-instructions.md");
45
+ const agentsMd = buildClaudeMd();
46
+ const context = [agentsMd, reminder].filter(Boolean).join("\n\n");
47
+ if (existsSync(instructionsPath) && lstatSync(instructionsPath).isSymbolicLink()) {
48
+ unlinkSync(instructionsPath);
49
+ }
50
+ writeFileSync(instructionsPath, context, "utf-8");
51
+ logDebug("LoadContext", `Copilot instructions written: ${context.length} chars`);
52
+ } else if (process.env.CURSOR_VERSION) {
37
53
  // Cursor: no native user-level rules — inject AGENTS.md + dynamic context
38
54
  const agentsMd = buildClaudeMd();
39
55
  const context = [agentsMd, reminder].filter(Boolean).join("\n\n");
40
56
  process.stdout.write(JSON.stringify({ additional_context: context }));
57
+ logDebug("LoadContext", `Reminder injected: ${reminder.length} chars`);
41
58
  } else {
42
59
  // Claude Code: raw text
43
60
  console.log(reminder);
61
+ logDebug("LoadContext", `Reminder injected: ${reminder.length} chars`);
44
62
  }
45
- logDebug("LoadContext", `Reminder injected: ${reminder.length} chars`);
46
63
  } catch (err) {
47
64
  logError("LoadContext:reminder", err);
48
65
  }
@@ -76,6 +76,11 @@ function ensureSymlinks(): void {
76
76
  const { outputPath, symlinkPath } = getOutputPaths();
77
77
  ensureOneSymlink(symlinkPath, outputPath);
78
78
  ensureOneSymlink(resolve(platform.codexDir(), "AGENTS.md"), outputPath);
79
+ // Copilot instructions — only create if ~/.copilot/ already exists (i.e. Copilot is installed)
80
+ const copilotDir = platform.copilotDir();
81
+ if (existsSync(copilotDir)) {
82
+ ensureOneSymlink(resolve(copilotDir, "copilot-instructions.md"), outputPath);
83
+ }
79
84
  }
80
85
 
81
86
  /** Returns true if AGENTS.md needs to be regenerated */
@@ -61,6 +61,7 @@ export const platform = {
61
61
  claudeDir: () => process.env.PAL_CLAUDE_DIR || resolve(h, ".claude"),
62
62
  opencodeDir: () => process.env.PAL_OPENCODE_DIR || resolve(h, ".config", "opencode"),
63
63
  cursorDir: () => process.env.PAL_CURSOR_DIR || resolve(h, ".cursor"),
64
+ copilotDir: () => process.env.PAL_COPILOT_DIR || resolve(h, ".copilot"),
64
65
  codexDir: () => process.env.PAL_CODEX_DIR || resolve(h, ".codex"),
65
66
  agentsDir: () => process.env.PAL_AGENTS_DIR || resolve(h, ".agents"),
66
67
  } as const;
@@ -74,6 +75,7 @@ export const assets = {
74
75
  agentsMdTemplate: () => pkg("assets", "templates", "AGENTS.md.template"),
75
76
  claudeSettingsTemplate: () => pkg("assets", "templates", "settings.claude.json"),
76
77
  cursorHooksTemplate: () => pkg("assets", "templates", "hooks.cursor.json"),
78
+ copilotHooksTemplate: () => pkg("assets", "templates", "hooks.copilot.json"),
77
79
  agentTools: () => pkg("src", "tools", "agent"),
78
80
  palDocs: () => pkg("assets", "templates", "PAL"),
79
81
  } as const;
@@ -0,0 +1,66 @@
1
+ /**
2
+ * PAL — Copilot target installer
3
+ * Writes hooks to ~/.copilot/hooks/pal-hooks.json.
4
+ * Copies skills and agents. Symlinks copilot-instructions.md to AGENTS.md.
5
+ */
6
+
7
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
8
+ import { resolve } from "node:path";
9
+ import { regenerateIfNeeded } from "../../hooks/lib/claude-md";
10
+ import { assets, palPkg, platform } from "../../hooks/lib/paths";
11
+ import {
12
+ copyAgentsForCopilot,
13
+ copyPalDocs,
14
+ copySkills,
15
+ countSkills,
16
+ generateSkillIndex,
17
+ loadCopilotHooksTemplate,
18
+ log,
19
+ scaffoldPalSettings,
20
+ } from "../lib";
21
+
22
+ const PKG_ROOT = palPkg().replaceAll("\\", "/");
23
+ const COPILOT_DIR = platform.copilotDir();
24
+ const HOOKS_DIR = resolve(COPILOT_DIR, "hooks");
25
+ const HOOKS_FILE = resolve(HOOKS_DIR, "pal-hooks.json");
26
+
27
+ // --- Ensure dirs ---
28
+ mkdirSync(HOOKS_DIR, { recursive: true });
29
+
30
+ // --- Write hooks file ---
31
+ const template = loadCopilotHooksTemplate(assets.copilotHooksTemplate(), PKG_ROOT);
32
+ writeFileSync(HOOKS_FILE, `${JSON.stringify(template, null, 2)}\n`, "utf-8");
33
+ log.success(`Written hooks to ${HOOKS_FILE}`);
34
+
35
+ // --- Install skills ---
36
+ const copilotSkillsDir = resolve(COPILOT_DIR, "skills");
37
+ copySkills(copilotSkillsDir);
38
+ generateSkillIndex();
39
+ log.success("Installed skills to ~/.copilot/skills/");
40
+
41
+ // --- Install agents ---
42
+ const copilotAgentsDir = resolve(COPILOT_DIR, "agents");
43
+ const agentCount = copyAgentsForCopilot(copilotAgentsDir);
44
+ if (agentCount > 0) log.success(`Installed ${agentCount} agents to ~/.copilot/agents/`);
45
+
46
+ // --- Copy PAL docs ---
47
+ const palDocsCount = copyPalDocs();
48
+ log.success(`Installed ${palDocsCount} PAL docs to ~/.pal/docs/`);
49
+
50
+ // --- Scaffold PAL settings ---
51
+ scaffoldPalSettings();
52
+
53
+ // --- Generate AGENTS.md + copilot-instructions.md symlink ---
54
+ // ensureSymlinks() inside regenerateIfNeeded() handles the symlink once ~/.copilot/ exists
55
+ regenerateIfNeeded();
56
+ const instructionsPath = resolve(COPILOT_DIR, "copilot-instructions.md");
57
+ log.success(
58
+ existsSync(instructionsPath)
59
+ ? "copilot-instructions.md symlink present"
60
+ : "Generated AGENTS.md (copilot-instructions.md symlink will be created on next session)"
61
+ );
62
+
63
+ log.success("Copilot installation complete");
64
+ console.log("");
65
+ log.info(`Skills: ${countSkills()}`);
66
+ log.info(`Hooks: ${HOOKS_FILE}`);
@@ -0,0 +1,60 @@
1
+ /**
2
+ * PAL — Copilot uninstaller
3
+ * Removes pal-hooks.json, skill symlinks, agents, and copilot-instructions.md symlink.
4
+ */
5
+
6
+ import { copyFileSync, existsSync, lstatSync, readlinkSync, unlinkSync } from "node:fs";
7
+ import { resolve } from "node:path";
8
+ import { platform } from "../../hooks/lib/paths";
9
+ import { log, removeAgentsFromCopilot, removePalDocs, removeSkills } from "../lib";
10
+
11
+ const COPILOT_DIR = platform.copilotDir();
12
+ const HOOKS_FILE = resolve(COPILOT_DIR, "hooks", "pal-hooks.json");
13
+
14
+ // --- Remove hooks file ---
15
+ if (existsSync(HOOKS_FILE)) {
16
+ copyFileSync(HOOKS_FILE, `${HOOKS_FILE}.bak.${Date.now()}`);
17
+ unlinkSync(HOOKS_FILE);
18
+ log.success("Removed pal-hooks.json");
19
+ } else {
20
+ log.info("No pal-hooks.json found, nothing to do");
21
+ }
22
+
23
+ // --- Remove skill symlinks ---
24
+ const copilotSkillsDir = resolve(COPILOT_DIR, "skills");
25
+ const removed = removeSkills(copilotSkillsDir);
26
+ if (removed.length > 0) {
27
+ log.success(`Removed ${removed.length} skill(s): ${removed.join(", ")}`);
28
+ } else {
29
+ log.info("No PAL skills found");
30
+ }
31
+
32
+ // --- Remove agents ---
33
+ const removedAgents = removeAgentsFromCopilot(resolve(COPILOT_DIR, "agents"));
34
+ if (removedAgents.length > 0) {
35
+ log.success(`Removed ${removedAgents.length} agent(s): ${removedAgents.join(", ")}`);
36
+ }
37
+
38
+ // --- Remove copilot-instructions.md symlink (only if it points to AGENTS.md) ---
39
+ const instructionsPath = resolve(COPILOT_DIR, "copilot-instructions.md");
40
+ if (existsSync(instructionsPath)) {
41
+ try {
42
+ const stat = lstatSync(instructionsPath);
43
+ if (stat.isSymbolicLink()) {
44
+ const target = readlinkSync(instructionsPath);
45
+ if (target.includes("AGENTS.md")) {
46
+ unlinkSync(instructionsPath);
47
+ log.success("Removed copilot-instructions.md symlink");
48
+ } else {
49
+ log.info("copilot-instructions.md is not a PAL symlink — leaving it");
50
+ }
51
+ }
52
+ } catch {
53
+ // ignore
54
+ }
55
+ }
56
+
57
+ // --- Remove PAL docs ---
58
+ removePalDocs();
59
+
60
+ log.success("Copilot uninstall complete");
@@ -432,7 +432,7 @@ export function countAgents(): number {
432
432
 
433
433
  // --- Agent platform extraction ---
434
434
 
435
- const AGENT_PLATFORMS = ["claude", "opencode", "cursor"] as const;
435
+ const AGENT_PLATFORMS = ["claude", "opencode", "cursor", "copilot"] as const;
436
436
  type AgentPlatform = (typeof AGENT_PLATFORMS)[number];
437
437
 
438
438
  /**
@@ -462,6 +462,7 @@ export function extractAgentForPlatform(
462
462
  claude: [],
463
463
  opencode: [],
464
464
  cursor: [],
465
+ copilot: [],
465
466
  };
466
467
  let currentPlatform: AgentPlatform | null = null;
467
468
 
@@ -542,10 +543,25 @@ export function copyAgentsForCursor(cursorAgentsDir: string): number {
542
543
  return installAgents(cursorAgentsDir, "cursor");
543
544
  }
544
545
 
546
+ export function copyAgentsForCopilot(copilotAgentsDir: string): number {
547
+ return installAgents(copilotAgentsDir, "copilot");
548
+ }
549
+
545
550
  export function removeAgentsFromCursor(cursorAgentsDir: string): string[] {
546
551
  return uninstallAgents(cursorAgentsDir, "cursor");
547
552
  }
548
553
 
554
+ export function removeAgentsFromCopilot(copilotAgentsDir: string): string[] {
555
+ return uninstallAgents(copilotAgentsDir, "copilot");
556
+ }
557
+
558
+ /** Load and resolve the Copilot hooks template, substituting PKG_ROOT */
559
+ export function loadCopilotHooksTemplate(templatePath: string, pkgRoot: string): unknown {
560
+ return JSON.parse(
561
+ readFileSync(templatePath, "utf-8").replaceAll("{{PKG_ROOT}}", pkgRoot)
562
+ );
563
+ }
564
+
549
565
  // --- Skill Index ---
550
566
 
551
567
  interface SkillIndexEntry {