@takara-ai/miru-code 1.5.3 → 1.7.2

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/src/help.ts CHANGED
@@ -37,24 +37,23 @@ export function printMainHelp(): void {
37
37
  commandRow("locate", "Exact substring location in the index");
38
38
  commandRow("expand", "Adjacent chunks in the same file as a hit");
39
39
  commandRow("find-related", "Find chunks related to a file:line");
40
- commandRow("setup", "Save your Takara API key locally");
40
+ commandRow("setup", "Authenticate with Takara");
41
41
  commandRow("install", "Configure miru across coding agents");
42
42
  commandRow("uninstall", "Remove miru agent configuration");
43
43
  commandRow("benchmark", "Turn MCP benchmark mode on or off");
44
44
  commandRow("init", "Write a project-local sub-agent file");
45
45
  commandRow("clear", "Remove cached index for a path");
46
- commandRow("help", "Show help for a command");
47
46
  divider();
48
47
 
49
48
  section("Quick start");
50
49
  writeStdout(" miru setup && miru install");
51
50
  writeStdout(' miru search "auth middleware" ./src');
52
51
  writeStdout("");
53
- hint("miru help <command> · miru -h for environment variables · miru -v for version");
52
+ hint("miru <command> -h · miru -v for version");
54
53
  writeStdout("");
55
54
  }
56
55
 
57
- export function printEnvHelp(): void {
56
+ export function printEnvironmentHelp(): void {
58
57
  section("Environment");
59
58
  writeStdout(` ${TAKARA_API_KEY_ENV}`);
60
59
  writeStdout(" Takara bearer token for embeddings");
@@ -67,6 +66,9 @@ export function printEnvHelp(): void {
67
66
  writeStdout(" MIRU_TOKENIZER_JSON");
68
67
  writeStdout(" Path to tokenizer.json (default: <package>/tokenizer/tokenizer.json)");
69
68
  writeStdout("");
69
+ }
70
+
71
+ export function printSageMakerHelp(): void {
70
72
  section("Self-hosted (AWS SageMaker)");
71
73
  writeStdout(" MIRU_SAGEMAKER_ENDPOINT_ARN");
72
74
  writeStdout(" arn:aws:sagemaker:<region>:<account-id>:endpoint/<name> — set this to");
@@ -90,13 +92,17 @@ export function printEnvHelp(): void {
90
92
  writeStdout("");
91
93
  }
92
94
 
93
- export function printFullHelp(): void {
94
- printMainHelp();
95
- printEnvHelp();
95
+ export function printEnvHelp(): void {
96
+ printEnvironmentHelp();
97
+ printSageMakerHelp();
96
98
  }
97
99
 
98
100
  export function printCommandHelp(command: string): void {
99
101
  switch (command) {
102
+ case "env":
103
+ case "environment":
104
+ printEnvironmentHelp();
105
+ return;
100
106
  case "search":
101
107
  commandHeader("search", "Hybrid semantic + keyword search.");
102
108
  section("Usage");
@@ -147,13 +153,14 @@ export function printCommandHelp(command: string): void {
147
153
  writeStdout("");
148
154
  return;
149
155
  case "setup":
150
- commandHeader("setup", "Store and validate Takara or self-hosted SageMaker credentials.");
156
+ commandHeader("setup", "Authenticate with Takara or self-hosted SageMaker credentials.");
151
157
  section("Usage");
152
- writeStdout(" miru setup [--key TOKEN] [--force] [--clear]");
158
+ writeStdout(" miru setup [--device] [--key TOKEN] [--force] [--clear]");
153
159
  writeStdout(" miru setup --sagemaker --arn ENDPOINT_ARN --profile NAME");
154
160
  section("Options (Takara)");
155
- writeStdout(" --key, -k TOKEN Non-interactive key entry");
156
- writeStdout(" --force Replace an existing stored key");
161
+ writeStdout(" --device Start device-code login (default when interactive)");
162
+ writeStdout(" --key, -k TOKEN Non-interactive: store a bearer token directly");
163
+ writeStdout(" --force Replace existing stored credentials");
157
164
  writeStdout(" --clear Remove stored credentials");
158
165
  section("Options (SageMaker)");
159
166
  writeStdout(" --sagemaker Switch setup to self-hosted SageMaker mode");
@@ -170,15 +177,24 @@ export function printCommandHelp(command: string): void {
170
177
  return;
171
178
  case "install":
172
179
  commandHeader("install", "Interactive global agent setup.");
173
- writeStdout("Configures MCP server, instructions, and sub-agent files under");
180
+ writeStdout("Configures MCP server, instructions, sub-agent files, and optional");
181
+ writeStdout("experimental integrations (search hooks, Caveman, STE writing) under");
174
182
  writeStdout("your user config (~/.claude, ~/.cursor, etc.).");
175
183
  writeStdout("");
184
+ writeStdout(
185
+ "Caveman = chat compression (/caveman). STE = clear docs (/ste). Off by default.",
186
+ );
187
+ writeStdout("Caveman is supported on all Miru installer IDEs (shared path for most IDEs).");
188
+ writeStdout(
189
+ "STE is supported on all Miru installer IDEs (same shared-path ownership model).",
190
+ );
176
191
  writeStdout("Run miru setup first, or set TAKARA_API_KEY for MCP env expansion.");
177
192
  writeStdout("");
178
193
  return;
179
194
  case "uninstall":
180
195
  commandHeader("uninstall", "Remove miru configuration from agents.");
181
- writeStdout("Removes MCP entries, marked instruction blocks, and global sub-agents.");
196
+ writeStdout("Removes MCP entries, marked instruction blocks, global sub-agents,");
197
+ writeStdout("and selected optional integrations (hooks, Caveman, STE skills).");
182
198
  writeStdout("Also deletes the global benchmark report from Miru's state directory.");
183
199
  writeStdout("");
184
200
  return;
@@ -9,6 +9,52 @@ function copilotHooksPath(home: string): string {
9
9
  return join(home, ".copilot", "hooks", "miru-search.json");
10
10
  }
11
11
 
12
+ /** Shared Copilot / VS Code / Visual Studio config root (MCP, hooks, agents). */
13
+ export function copilotHomeDir(home: string): string {
14
+ return join(home, ".copilot");
15
+ }
16
+
17
+ /**
18
+ * Vendors that do not scan `~/.agents/skills/` and need a native skill dir
19
+ * (Caveman + STE).
20
+ */
21
+ export type NativeSkillVendor = "claude" | "kiro";
22
+
23
+ /** @deprecated Use NativeSkillVendor. */
24
+ export type NativeCavemanVendor = NativeSkillVendor;
25
+
26
+ function skillMd(home: string, rootDir: string, skillName: string): string {
27
+ return join(home, rootDir, "skills", skillName, "SKILL.md");
28
+ }
29
+
30
+ function skillDir(home: string, rootDir: string, skillName: string): string {
31
+ return join(home, rootDir, "skills", skillName);
32
+ }
33
+
34
+ /**
35
+ * Cross-agent Agent Skills root (`~/.agents/skills/…`).
36
+ * Used by Cursor, Gemini, OpenCode, Codex, Windsurf, and Copilot-family IDEs.
37
+ * Claude Code and Kiro still use vendor-native skill dirs.
38
+ */
39
+ export function agentsCavemanSkillPath(home: string): string {
40
+ return skillMd(home, ".agents", "caveman");
41
+ }
42
+
43
+ /** Claude Code / Kiro native Caveman skill path. */
44
+ export function nativeCavemanSkillPath(home: string, vendor: NativeSkillVendor): string {
45
+ return skillMd(home, `.${vendor}`, "caveman");
46
+ }
47
+
48
+ /** Shared STE skill directory under `~/.agents/skills/ste`. */
49
+ export function agentsSteSkillDir(home: string): string {
50
+ return skillDir(home, ".agents", "ste");
51
+ }
52
+
53
+ /** Claude Code / Kiro native STE skill directory. */
54
+ export function nativeSteSkillDir(home: string, vendor: NativeSkillVendor): string {
55
+ return skillDir(home, `.${vendor}`, "ste");
56
+ }
57
+
12
58
  function kiroHooksPath(home: string): string {
13
59
  return join(home, ".kiro", "settings", "hooks.json");
14
60
  }
@@ -17,10 +63,14 @@ function windsurfHooksPath(home: string): string {
17
63
  return join(home, ".codeium", "windsurf", "hooks.json");
18
64
  }
19
65
 
20
- function opencodePluginPath(home: string): string {
66
+ /** OpenCode config root (`$XDG_CONFIG_HOME/opencode` or `~/.config/opencode`). */
67
+ export function opencodeConfigDir(home: string): string {
21
68
  const xdg = process.env.XDG_CONFIG_HOME;
22
- const base = xdg ? join(xdg, "opencode") : join(home, ".config", "opencode");
23
- return join(base, "plugins", "miru-search-guard.ts");
69
+ return xdg ? join(xdg, "opencode") : join(home, ".config", "opencode");
70
+ }
71
+
72
+ function opencodePluginPath(home: string): string {
73
+ return join(opencodeConfigDir(home), "plugins", "miru-search-guard.ts");
24
74
  }
25
75
 
26
76
  export type InstallAction =
@@ -92,11 +142,19 @@ export interface AgentTarget {
92
142
  hooksFormat: HooksFormat | null;
93
143
  subagentPath: string | null;
94
144
  subagentId: AgentId | null;
145
+ /**
146
+ * On-demand Caveman Agent Skill (`…/skills/caveman/SKILL.md`), or null if
147
+ * unsupported.
148
+ */
149
+ cavemanSkillPath: string | null;
150
+ /**
151
+ * On-demand STE skill directory (`…/skills/ste/`), or null if unsupported.
152
+ */
153
+ steSkillDir: string | null;
95
154
  }
96
155
 
97
156
  export function opencodeMcpPath(): string {
98
- const xdg = process.env.XDG_CONFIG_HOME;
99
- const base = xdg ? join(xdg, "opencode") : join(HOME, ".config", "opencode");
157
+ const base = opencodeConfigDir(HOME);
100
158
  const jsonc = join(base, "opencode.jsonc");
101
159
  const json = join(base, "opencode.json");
102
160
  if (existsSync(jsonc)) {
@@ -163,6 +221,9 @@ function jsonMcp(path: string, key: string, entry: Record<string, unknown>): Mcp
163
221
  return { path, key, memberKey: "miru", entry, format: "json" };
164
222
  }
165
223
 
224
+ const SHARED_CAVEMAN_SKILL = agentsCavemanSkillPath(HOME);
225
+ const SHARED_STE_SKILL_DIR = agentsSteSkillDir(HOME);
226
+
166
227
  export const AGENT_TARGETS: AgentTarget[] = [
167
228
  {
168
229
  id: "claude",
@@ -176,6 +237,8 @@ export const AGENT_TARGETS: AgentTarget[] = [
176
237
  hooksFormat: "claude",
177
238
  subagentPath: join(HOME, ".claude", "agents", "miru-code.md"),
178
239
  subagentId: "claude",
240
+ cavemanSkillPath: nativeCavemanSkillPath(HOME, "claude"),
241
+ steSkillDir: nativeSteSkillDir(HOME, "claude"),
179
242
  },
180
243
  {
181
244
  id: "cursor",
@@ -189,6 +252,8 @@ export const AGENT_TARGETS: AgentTarget[] = [
189
252
  hooksFormat: "cursor",
190
253
  subagentPath: join(HOME, ".cursor", "agents", "miru-code.md"),
191
254
  subagentId: "cursor",
255
+ cavemanSkillPath: SHARED_CAVEMAN_SKILL,
256
+ steSkillDir: SHARED_STE_SKILL_DIR,
192
257
  },
193
258
  {
194
259
  id: "gemini",
@@ -202,50 +267,57 @@ export const AGENT_TARGETS: AgentTarget[] = [
202
267
  hooksFormat: "gemini",
203
268
  subagentPath: join(HOME, ".gemini", "agents", "miru-code.md"),
204
269
  subagentId: "gemini",
270
+ cavemanSkillPath: SHARED_CAVEMAN_SKILL,
271
+ steSkillDir: SHARED_STE_SKILL_DIR,
205
272
  },
206
273
  {
207
274
  id: "kiro",
208
275
  displayName: "Kiro",
209
276
  binary: "kiro",
210
277
  configDir: join(HOME, ".kiro"),
211
- mcp: jsonMcp(join(HOME, ".kiro", "settings", "mcp.json"), "mcpServers", {
212
- ...STDIO_SERVER_CONFIG,
213
- env: {
214
- PATH: "/usr/local/bin:/usr/bin:/bin",
215
- },
216
- }),
278
+ mcp: jsonMcp(join(HOME, ".kiro", "settings", "mcp.json"), "mcpServers", STDIO_SERVER_CONFIG),
217
279
  instructionsPath: join(HOME, ".kiro", "steering", "miru.md"),
218
280
  cursorRulesPath: null,
219
281
  hooksPath: kiroHooksPath(HOME),
220
282
  hooksFormat: "kiro",
221
283
  subagentPath: join(HOME, ".kiro", "agents", "miru-code.md"),
222
284
  subagentId: "kiro",
285
+ cavemanSkillPath: nativeCavemanSkillPath(HOME, "kiro"),
286
+ steSkillDir: nativeSteSkillDir(HOME, "kiro"),
223
287
  },
224
288
  {
225
289
  id: "opencode",
226
290
  displayName: "OpenCode",
227
291
  binary: "opencode",
228
- configDir: join(HOME, ".config", "opencode"),
292
+ configDir: opencodeConfigDir(HOME),
229
293
  mcp: jsonMcp(opencodeMcpPath(), "mcp", OPENCODE_SERVER_CONFIG),
230
- instructionsPath: join(HOME, ".config", "opencode", "AGENTS.md"),
294
+ instructionsPath: join(opencodeConfigDir(HOME), "AGENTS.md"),
231
295
  cursorRulesPath: null,
232
296
  hooksPath: opencodePluginPath(HOME),
233
297
  hooksFormat: "opencode",
234
- subagentPath: join(HOME, ".config", "opencode", "agents", "miru-code.md"),
298
+ subagentPath: join(opencodeConfigDir(HOME), "agents", "miru-code.md"),
235
299
  subagentId: "opencode",
300
+ cavemanSkillPath: SHARED_CAVEMAN_SKILL,
301
+ steSkillDir: SHARED_STE_SKILL_DIR,
236
302
  },
237
303
  {
238
304
  id: "copilot",
239
305
  displayName: "GitHub Copilot",
240
306
  binary: null,
241
307
  configDir: join(HOME, ".config", "github-copilot"),
242
- mcp: jsonMcp(join(HOME, ".copilot", "mcp-config.json"), "mcpServers", BARE_STDIO_SERVER_CONFIG),
308
+ mcp: jsonMcp(
309
+ join(copilotHomeDir(HOME), "mcp-config.json"),
310
+ "mcpServers",
311
+ BARE_STDIO_SERVER_CONFIG,
312
+ ),
243
313
  instructionsPath: null,
244
314
  cursorRulesPath: null,
245
315
  hooksPath: copilotHooksPath(HOME),
246
316
  hooksFormat: "vscode",
247
- subagentPath: join(HOME, ".copilot", "agents", "miru-code.agent.md"),
317
+ subagentPath: join(copilotHomeDir(HOME), "agents", "miru-code.agent.md"),
248
318
  subagentId: "copilot",
319
+ cavemanSkillPath: SHARED_CAVEMAN_SKILL,
320
+ steSkillDir: SHARED_STE_SKILL_DIR,
249
321
  },
250
322
  {
251
323
  id: "codex",
@@ -265,6 +337,8 @@ export const AGENT_TARGETS: AgentTarget[] = [
265
337
  hooksFormat: "claude",
266
338
  subagentPath: null,
267
339
  subagentId: null,
340
+ cavemanSkillPath: SHARED_CAVEMAN_SKILL,
341
+ steSkillDir: SHARED_STE_SKILL_DIR,
268
342
  },
269
343
  {
270
344
  id: "vscode",
@@ -278,6 +352,8 @@ export const AGENT_TARGETS: AgentTarget[] = [
278
352
  hooksFormat: "vscode",
279
353
  subagentPath: null,
280
354
  subagentId: null,
355
+ cavemanSkillPath: SHARED_CAVEMAN_SKILL,
356
+ steSkillDir: SHARED_STE_SKILL_DIR,
281
357
  },
282
358
  {
283
359
  id: "windsurf",
@@ -291,6 +367,8 @@ export const AGENT_TARGETS: AgentTarget[] = [
291
367
  hooksFormat: "windsurf",
292
368
  subagentPath: null,
293
369
  subagentId: null,
370
+ cavemanSkillPath: SHARED_CAVEMAN_SKILL,
371
+ steSkillDir: SHARED_STE_SKILL_DIR,
294
372
  },
295
373
  {
296
374
  id: "visualstudio",
@@ -304,6 +382,8 @@ export const AGENT_TARGETS: AgentTarget[] = [
304
382
  hooksFormat: "vscode",
305
383
  subagentPath: null,
306
384
  subagentId: null,
385
+ cavemanSkillPath: SHARED_CAVEMAN_SKILL,
386
+ steSkillDir: SHARED_STE_SKILL_DIR,
307
387
  },
308
388
  ];
309
389
 
@@ -330,6 +410,10 @@ export async function isAgentDetected(agent: AgentTarget): Promise<boolean> {
330
410
  }
331
411
  return false;
332
412
  }
413
+ // Copilot-specific artifacts only — ~/.copilot alone is shared with VS Code / VS.
414
+ if (agent.id === "copilot") {
415
+ return isCopilotInstalled(HOME);
416
+ }
333
417
  if (agent.binary && (await commandOnPath(agent.binary))) {
334
418
  return true;
335
419
  }
@@ -338,3 +422,21 @@ export async function isAgentDetected(agent: AgentTarget): Promise<boolean> {
338
422
  }
339
423
  return false;
340
424
  }
425
+
426
+ /**
427
+ * True when Copilot-specific config exists. Does not treat shared
428
+ * `~/.copilot` hooks (also used by VS Code / Visual Studio) as enough.
429
+ */
430
+ export function isCopilotInstalled(home: string = HOME): boolean {
431
+ if (existsSync(join(home, ".config", "github-copilot"))) {
432
+ return true;
433
+ }
434
+ const copilot = copilotHomeDir(home);
435
+ if (existsSync(join(copilot, "mcp-config.json"))) {
436
+ return true;
437
+ }
438
+ if (existsSync(join(copilot, "agents"))) {
439
+ return true;
440
+ }
441
+ return false;
442
+ }
@@ -0,0 +1,51 @@
1
+ import type { AgentTarget, InstallAction } from "./agents.ts";
2
+ import {
3
+ ensureSharedSkillOwnersOnInstall,
4
+ isSharedSkillPath,
5
+ removeSkillMdAndOwners,
6
+ resolveSharedSkillUninstall,
7
+ type SharedSkillCtx,
8
+ type SharedSkillUninstallDecision,
9
+ SKILL_OWNERS_FILE,
10
+ type SkillPathOf,
11
+ } from "./shared-skill.ts";
12
+
13
+ /** @deprecated Prefer SKILL_OWNERS_FILE — same sidecar name for all shared skills. */
14
+ export const CAVEMAN_OWNERS_FILE = SKILL_OWNERS_FILE;
15
+
16
+ /** Context for shared `~/.agents/skills` Caveman install/uninstall. */
17
+ export type CavemanSharedCtx = SharedSkillCtx;
18
+
19
+ export type SharedCavemanUninstallDecision = SharedSkillUninstallDecision;
20
+
21
+ const cavemanPathOf: SkillPathOf = (agent) => agent.cavemanSkillPath;
22
+
23
+ export function isSharedCavemanPath(path: string, allAgents: AgentTarget[]): boolean {
24
+ return isSharedSkillPath(path, allAgents, cavemanPathOf);
25
+ }
26
+
27
+ export async function ensureSharedCavemanOwnersOnInstall(
28
+ skillDir: string,
29
+ skillPath: string,
30
+ agent: AgentTarget,
31
+ ctx: CavemanSharedCtx,
32
+ ): Promise<void> {
33
+ return ensureSharedSkillOwnersOnInstall(skillDir, skillPath, agent, ctx, cavemanPathOf);
34
+ }
35
+
36
+ export async function resolveSharedCavemanUninstall(
37
+ path: string,
38
+ skillDir: string,
39
+ agent: AgentTarget,
40
+ ctx: CavemanSharedCtx,
41
+ ): Promise<SharedCavemanUninstallDecision> {
42
+ return resolveSharedSkillUninstall(path, skillDir, agent, ctx, cavemanPathOf);
43
+ }
44
+
45
+ /** Remove SKILL.md and owners sidecar; leave sibling skill folders untouched. */
46
+ export async function removeCavemanSkillFiles(
47
+ path: string,
48
+ skillDir: string,
49
+ ): Promise<Extract<InstallAction, "removed" | "not-found">> {
50
+ return removeSkillMdAndOwners(path, skillDir);
51
+ }
@@ -616,3 +616,76 @@ export async function removeTomlBlock(path: string): Promise<InstallAction> {
616
616
  await Bun.write(path, `${remaining}\n`);
617
617
  return "removed";
618
618
  }
619
+
620
+ /**
621
+ * Ensure Codex loads Agent Skills: `[features] skills = true` in config.toml.
622
+ * Does not disable the flag on uninstall (other skills may still need it).
623
+ */
624
+ export function codexSkillsFeatureEnabled(text: string): boolean {
625
+ const lines = text.split("\n");
626
+ let inFeatures = false;
627
+ for (const line of lines) {
628
+ const tableKey = line.split("#")[0]?.trim() ?? "";
629
+ if (tableKey.startsWith("[") && tableKey.endsWith("]")) {
630
+ inFeatures = tableKey === "[features]";
631
+ continue;
632
+ }
633
+ if (!inFeatures) {
634
+ continue;
635
+ }
636
+ const value = line.split("#")[0]?.match(/^\s*skills\s*=\s*(true|false)\s*$/i)?.[1];
637
+ if (value?.toLowerCase() === "true") {
638
+ return true;
639
+ }
640
+ }
641
+ return false;
642
+ }
643
+
644
+ export async function ensureCodexSkillsFeature(path: string): Promise<InstallAction> {
645
+ const existed = await Bun.file(path).exists();
646
+ const existing = existed ? await Bun.file(path).text() : "";
647
+ if (codexSkillsFeatureEnabled(existing)) {
648
+ return "unchanged";
649
+ }
650
+ const lines = existing.length > 0 ? existing.split("\n") : [];
651
+
652
+ let featuresIdx = -1;
653
+ let skillsIdx = -1;
654
+ let inFeatures = false;
655
+
656
+ for (let i = 0; i < lines.length; i++) {
657
+ const tableKey = (lines[i] ?? "").split("#")[0]?.trim() ?? "";
658
+ if (tableKey.startsWith("[") && tableKey.endsWith("]")) {
659
+ if (tableKey === "[features]") {
660
+ inFeatures = true;
661
+ featuresIdx = i;
662
+ continue;
663
+ }
664
+ inFeatures = false;
665
+ continue;
666
+ }
667
+ if (inFeatures && /^\s*skills\s*=/.test(lines[i] ?? "")) {
668
+ skillsIdx = i;
669
+ }
670
+ }
671
+
672
+ if (featuresIdx >= 0 && skillsIdx >= 0) {
673
+ lines[skillsIdx] = "skills = true";
674
+ const next = `${lines.join("\n").replace(/\n+$/, "")}\n`;
675
+ await Bun.write(path, next);
676
+ return "updated";
677
+ }
678
+
679
+ if (featuresIdx >= 0) {
680
+ lines.splice(featuresIdx + 1, 0, "skills = true");
681
+ const next = `${lines.join("\n").replace(/\n+$/, "")}\n`;
682
+ await Bun.write(path, next);
683
+ return "updated";
684
+ }
685
+
686
+ const block = "[features]\nskills = true\n";
687
+ const base = existing.replace(/\n+$/, "");
688
+ const next = base.length > 0 ? `${base}\n\n${block}` : block;
689
+ await Bun.write(path, next.endsWith("\n") ? next : `${next}\n`);
690
+ return existed ? "updated" : "created";
691
+ }
@@ -0,0 +1,8 @@
1
+ /** Agents that share `~/.copilot` hooks. Caveman uses `~/.agents/skills`. */
2
+ export const COPILOT_FAMILY_IDS = ["copilot", "vscode", "visualstudio"] as const;
3
+
4
+ export type CopilotFamilyId = (typeof COPILOT_FAMILY_IDS)[number];
5
+
6
+ export function isCopilotFamilyId(id: string): id is CopilotFamilyId {
7
+ return (COPILOT_FAMILY_IDS as readonly string[]).includes(id);
8
+ }
@@ -2,11 +2,11 @@ import { unlink } from "node:fs/promises";
2
2
  import { join, resolve } from "node:path";
3
3
  import type { HooksFormat, InstallAction } from "../agents.ts";
4
4
  import { stripJsonComments } from "../config.ts";
5
+ import { isCopilotFamilyId } from "../copilot-family.ts";
5
6
 
6
7
  const HOOK_GUARD_MARKER = "hook-guard";
7
8
  const MIRU_HOOK_MARKER = "miru-search";
8
9
  const VSCODE_OWNERS_KEY = "miruOwners";
9
- const SHARED_VSCODE_HOOK_OWNERS = ["copilot", "vscode", "visualstudio"] as const;
10
10
 
11
11
  const MATCHERS = {
12
12
  cursor: "Grep|Glob|Shell|SemanticSearch",
@@ -346,22 +346,11 @@ export async function mergeVscodeHooks(path: string, owner?: string): Promise<In
346
346
 
347
347
  const currentOwners = Array.isArray(parsed[VSCODE_OWNERS_KEY])
348
348
  ? (parsed[VSCODE_OWNERS_KEY] as unknown[]).filter(
349
- (item): item is string =>
350
- typeof item === "string" &&
351
- SHARED_VSCODE_HOOK_OWNERS.includes(item as (typeof SHARED_VSCODE_HOOK_OWNERS)[number]),
349
+ (item): item is string => typeof item === "string" && isCopilotFamilyId(item),
352
350
  )
353
351
  : [];
354
352
  const nextOwners = owner
355
- ? Array.from(
356
- new Set([
357
- ...currentOwners,
358
- ...(SHARED_VSCODE_HOOK_OWNERS.includes(
359
- owner as (typeof SHARED_VSCODE_HOOK_OWNERS)[number],
360
- )
361
- ? [owner]
362
- : []),
363
- ]),
364
- )
353
+ ? Array.from(new Set([...currentOwners, ...(isCopilotFamilyId(owner) ? [owner] : [])]))
365
354
  : currentOwners;
366
355
 
367
356
  const next = {