@takara-ai/miru-code 0.4.5 → 0.4.9

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.
@@ -1,7 +1,27 @@
1
1
  import { existsSync } from "node:fs";
2
+ import { readdir } from "node:fs/promises";
2
3
  import { homedir } from "node:os";
3
4
  import { join } from "node:path";
4
5
  import type { AgentId } from "../agents.ts";
6
+ import { INSTRUCTIONS_MARKDOWN } from "./search-policy.ts";
7
+
8
+ function copilotHooksPath(home: string): string {
9
+ return join(home, ".copilot", "hooks", "miru-search.json");
10
+ }
11
+
12
+ function kiroHooksPath(home: string): string {
13
+ return join(home, ".kiro", "settings", "hooks.json");
14
+ }
15
+
16
+ function windsurfHooksPath(home: string): string {
17
+ return join(home, ".codeium", "windsurf", "hooks.json");
18
+ }
19
+
20
+ function opencodePluginPath(home: string): string {
21
+ 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");
24
+ }
5
25
 
6
26
  export type InstallAction =
7
27
  | "created"
@@ -19,65 +39,25 @@ export const MIRU_END = "<!-- miru:end -->";
19
39
 
20
40
  const HOME = homedir();
21
41
 
22
- const TAKARA_KEY_PLACEHOLDER = "$" + "{TAKARA_API_KEY}";
23
-
24
- const MCP_ENV: Record<string, string> = {
25
- TAKARA_API_KEY: TAKARA_KEY_PLACEHOLDER,
26
- };
27
-
28
42
  const STDIO_SERVER_CONFIG: Record<string, unknown> = {
29
43
  command: "bunx",
30
44
  args: ["@takara-ai/miru-code"],
31
45
  type: "stdio",
32
- env: MCP_ENV,
33
46
  };
34
47
 
35
48
  const BARE_STDIO_SERVER_CONFIG: Record<string, unknown> = {
36
49
  command: "bunx",
37
50
  args: ["@takara-ai/miru-code"],
38
- env: MCP_ENV,
39
51
  };
40
52
 
41
53
  const OPENCODE_SERVER_CONFIG: Record<string, unknown> = {
42
54
  command: ["bunx", "@takara-ai/miru-code"],
43
55
  type: "local",
44
56
  enabled: true,
45
- environment: MCP_ENV,
46
57
  };
47
58
 
48
59
  export const INSTRUCTIONS = `${MIRU_START}
49
- ## Miru Code Search
50
-
51
- A \`miru\` MCP server is available with two tools:
52
- - \`search\` — search the codebase with a natural-language or code query.
53
- - \`find_related\` — find code similar to a specific file and line.
54
-
55
- Always call \`search\` before using Grep, Glob, or Read to explore the codebase. Use Grep/Glob/Read only for exact path lookup, exhaustive literal matches, or when the returned chunk lacks enough context.
56
-
57
- Run \`miru setup\` once to store your API key — the MCP server loads it from \`credentials.json\`. Optionally set \`TAKARA_API_KEY\` in MCP config to override.
58
-
59
- Pass \`--content docs\` to search documentation, \`--content config\` for config files, or \`--content all\` for everything.
60
-
61
- For CLI fallback or sub-agents without MCP access:
62
-
63
- \`\`\`bash
64
- miru search "authentication flow" ./my-project
65
- miru search "deployment guide" ./my-project --content docs
66
- miru find-related src/auth.ts 42 ./my-project
67
- miru search "save model to disk" ./my-project --top-k 10
68
- \`\`\`
69
-
70
- First run builds a disk cache. The MCP server updates local indexes while it runs; after CLI-only use or large refactors, run \`miru clear <path>\`.
71
-
72
- If \`miru\` is not on \`$PATH\`, use \`bunx @takara-ai/miru-code\` in its place.
73
-
74
- ### Workflow
75
-
76
- 1. Start with \`search\` (MCP) or \`miru search\` (CLI) to find relevant chunks.
77
- 2. Use \`--content docs\` / \`--content config\` / \`--content all\` when appropriate.
78
- 3. Inspect full files only when the chunk is not enough.
79
- 4. Optionally use \`find_related\` with a hit's \`file_path\` and \`line\`.
80
- 5. Use Grep only for exhaustive literal matches.
60
+ ${INSTRUCTIONS_MARKDOWN}
81
61
  ${MIRU_END}
82
62
  `;
83
63
 
@@ -91,13 +71,25 @@ export interface McpConfig {
91
71
  format: McpConfigFormat;
92
72
  }
93
73
 
74
+ export type HooksFormat =
75
+ | "claude"
76
+ | "cursor"
77
+ | "gemini"
78
+ | "vscode"
79
+ | "kiro"
80
+ | "windsurf"
81
+ | "opencode";
82
+
94
83
  export interface AgentTarget {
95
- id: AgentId | "codex" | "vscode";
84
+ id: AgentId | "codex" | "vscode" | "visualstudio" | "windsurf";
96
85
  displayName: string;
97
86
  binary: string | null;
98
87
  configDir: string | null;
99
88
  mcp: McpConfig | null;
100
89
  instructionsPath: string | null;
90
+ cursorRulesPath: string | null;
91
+ hooksPath: string | null;
92
+ hooksFormat: HooksFormat | null;
101
93
  subagentPath: string | null;
102
94
  subagentId: AgentId | null;
103
95
  }
@@ -128,6 +120,45 @@ export function vscodeMcpPath(): string {
128
120
  return join(xdg, "Code", "User", "mcp.json");
129
121
  }
130
122
 
123
+ /** Global MCP config for Visual Studio (GitHub Copilot Agent Mode). */
124
+ export function visualStudioMcpPath(): string {
125
+ const profile = process.env.USERPROFILE ?? HOME;
126
+ return join(profile, ".mcp.json");
127
+ }
128
+
129
+ export function visualStudioInstallDir(): string | null {
130
+ if (process.platform !== "win32") {
131
+ return null;
132
+ }
133
+ const programFiles = process.env.ProgramFiles ?? join("C:", "Program Files");
134
+ return join(programFiles, "Microsoft Visual Studio");
135
+ }
136
+
137
+ async function detectVisualStudio(): Promise<boolean> {
138
+ if (process.platform !== "win32") {
139
+ return false;
140
+ }
141
+
142
+ const installDir = visualStudioInstallDir();
143
+ if (installDir && existsSync(installDir)) {
144
+ try {
145
+ const entries = await readdir(installDir);
146
+ if (entries.length > 0) {
147
+ return true;
148
+ }
149
+ } catch {
150
+ // ignore unreadable install directory
151
+ }
152
+ }
153
+
154
+ try {
155
+ const proc = Bun.spawn(["where", "devenv"], { stdout: "pipe", stderr: "ignore" });
156
+ return (await proc.exited) === 0;
157
+ } catch {
158
+ return false;
159
+ }
160
+ }
161
+
131
162
  function jsonMcp(path: string, key: string, entry: Record<string, unknown>): McpConfig {
132
163
  return { path, key, memberKey: "miru", entry, format: "json" };
133
164
  }
@@ -140,6 +171,9 @@ export const AGENT_TARGETS: AgentTarget[] = [
140
171
  configDir: join(HOME, ".claude"),
141
172
  mcp: jsonMcp(join(HOME, ".claude.json"), "mcpServers", STDIO_SERVER_CONFIG),
142
173
  instructionsPath: join(HOME, ".claude", "CLAUDE.md"),
174
+ cursorRulesPath: null,
175
+ hooksPath: join(HOME, ".claude", "settings.json"),
176
+ hooksFormat: "claude",
143
177
  subagentPath: join(HOME, ".claude", "agents", "miru-code.md"),
144
178
  subagentId: "claude",
145
179
  },
@@ -150,6 +184,9 @@ export const AGENT_TARGETS: AgentTarget[] = [
150
184
  configDir: join(HOME, ".cursor"),
151
185
  mcp: jsonMcp(join(HOME, ".cursor", "mcp.json"), "mcpServers", STDIO_SERVER_CONFIG),
152
186
  instructionsPath: null,
187
+ cursorRulesPath: join(HOME, ".cursor", "rules", "miru-code.mdc"),
188
+ hooksPath: join(HOME, ".cursor", "hooks.json"),
189
+ hooksFormat: "cursor",
153
190
  subagentPath: join(HOME, ".cursor", "agents", "miru-code.md"),
154
191
  subagentId: "cursor",
155
192
  },
@@ -160,6 +197,9 @@ export const AGENT_TARGETS: AgentTarget[] = [
160
197
  configDir: join(HOME, ".gemini"),
161
198
  mcp: jsonMcp(join(HOME, ".gemini", "settings.json"), "mcpServers", STDIO_SERVER_CONFIG),
162
199
  instructionsPath: join(HOME, ".gemini", "GEMINI.md"),
200
+ cursorRulesPath: null,
201
+ hooksPath: join(HOME, ".gemini", "settings.json"),
202
+ hooksFormat: "gemini",
163
203
  subagentPath: join(HOME, ".gemini", "agents", "miru-code.md"),
164
204
  subagentId: "gemini",
165
205
  },
@@ -171,11 +211,13 @@ export const AGENT_TARGETS: AgentTarget[] = [
171
211
  mcp: jsonMcp(join(HOME, ".kiro", "settings", "mcp.json"), "mcpServers", {
172
212
  ...STDIO_SERVER_CONFIG,
173
213
  env: {
174
- ...MCP_ENV,
175
214
  PATH: "/usr/local/bin:/usr/bin:/bin",
176
215
  },
177
216
  }),
178
217
  instructionsPath: join(HOME, ".kiro", "steering", "miru.md"),
218
+ cursorRulesPath: null,
219
+ hooksPath: kiroHooksPath(HOME),
220
+ hooksFormat: "kiro",
179
221
  subagentPath: join(HOME, ".kiro", "agents", "miru-code.md"),
180
222
  subagentId: "kiro",
181
223
  },
@@ -186,6 +228,9 @@ export const AGENT_TARGETS: AgentTarget[] = [
186
228
  configDir: join(HOME, ".config", "opencode"),
187
229
  mcp: jsonMcp(opencodeMcpPath(), "mcp", OPENCODE_SERVER_CONFIG),
188
230
  instructionsPath: join(HOME, ".config", "opencode", "AGENTS.md"),
231
+ cursorRulesPath: null,
232
+ hooksPath: opencodePluginPath(HOME),
233
+ hooksFormat: "opencode",
189
234
  subagentPath: join(HOME, ".config", "opencode", "agents", "miru-code.md"),
190
235
  subagentId: "opencode",
191
236
  },
@@ -196,6 +241,9 @@ export const AGENT_TARGETS: AgentTarget[] = [
196
241
  configDir: join(HOME, ".config", "github-copilot"),
197
242
  mcp: jsonMcp(join(HOME, ".copilot", "mcp-config.json"), "mcpServers", BARE_STDIO_SERVER_CONFIG),
198
243
  instructionsPath: null,
244
+ cursorRulesPath: null,
245
+ hooksPath: copilotHooksPath(HOME),
246
+ hooksFormat: "vscode",
199
247
  subagentPath: join(HOME, ".copilot", "agents", "miru-code.agent.md"),
200
248
  subagentId: "copilot",
201
249
  },
@@ -212,6 +260,9 @@ export const AGENT_TARGETS: AgentTarget[] = [
212
260
  format: "toml",
213
261
  },
214
262
  instructionsPath: join(HOME, ".codex", "AGENTS.md"),
263
+ cursorRulesPath: null,
264
+ hooksPath: join(HOME, ".codex", "hooks.json"),
265
+ hooksFormat: "claude",
215
266
  subagentPath: null,
216
267
  subagentId: null,
217
268
  },
@@ -222,22 +273,65 @@ export const AGENT_TARGETS: AgentTarget[] = [
222
273
  configDir: null,
223
274
  mcp: jsonMcp(vscodeMcpPath(), "servers", STDIO_SERVER_CONFIG),
224
275
  instructionsPath: null,
276
+ cursorRulesPath: null,
277
+ hooksPath: copilotHooksPath(HOME),
278
+ hooksFormat: "vscode",
279
+ subagentPath: null,
280
+ subagentId: null,
281
+ },
282
+ {
283
+ id: "windsurf",
284
+ displayName: "Windsurf / Devin Desktop",
285
+ binary: "windsurf",
286
+ configDir: join(HOME, ".codeium", "windsurf"),
287
+ mcp: null,
288
+ instructionsPath: null,
289
+ cursorRulesPath: null,
290
+ hooksPath: windsurfHooksPath(HOME),
291
+ hooksFormat: "windsurf",
292
+ subagentPath: null,
293
+ subagentId: null,
294
+ },
295
+ {
296
+ id: "visualstudio",
297
+ displayName: process.platform === "win32" ? "Visual Studio" : "Visual Studio (Windows)",
298
+ binary: null,
299
+ configDir: visualStudioInstallDir(),
300
+ mcp: jsonMcp(visualStudioMcpPath(), "servers", STDIO_SERVER_CONFIG),
301
+ instructionsPath: null,
302
+ cursorRulesPath: null,
303
+ hooksPath: copilotHooksPath(HOME),
304
+ hooksFormat: "vscode",
225
305
  subagentPath: null,
226
306
  subagentId: null,
227
307
  },
228
308
  ];
229
309
 
310
+ async function commandOnPath(command: string): Promise<boolean> {
311
+ const lookup = process.platform === "win32" ? ["where", command] : ["which", command];
312
+ try {
313
+ const proc = Bun.spawn(lookup, { stdout: "pipe", stderr: "ignore" });
314
+ return (await proc.exited) === 0;
315
+ } catch {
316
+ return false;
317
+ }
318
+ }
319
+
230
320
  export async function isAgentDetected(agent: AgentTarget): Promise<boolean> {
231
- if (agent.binary) {
232
- try {
233
- const proc = Bun.spawn(["which", agent.binary], { stdout: "pipe", stderr: "ignore" });
234
- const code = await proc.exited;
235
- if (code === 0) {
236
- return true;
237
- }
238
- } catch {
239
- // which unavailable
321
+ if (agent.id === "visualstudio") {
322
+ return detectVisualStudio();
323
+ }
324
+ if (agent.id === "windsurf") {
325
+ if (agent.configDir && existsSync(agent.configDir)) {
326
+ return true;
327
+ }
328
+ if (agent.binary && (await commandOnPath(agent.binary))) {
329
+ return true;
240
330
  }
331
+ return false;
332
+ }
333
+ if (agent.binary && (await commandOnPath(agent.binary))) {
334
+ return true;
241
335
  }
242
336
  if (agent.configDir) {
243
337
  return Bun.file(agent.configDir).exists();
@@ -6,7 +6,6 @@ const CODEX_MCP_HEADER = "[mcp_servers.miru]";
6
6
  const CODEX_MCP_BLOCK = `[mcp_servers.miru]
7
7
  command = "bunx"
8
8
  args = ["@takara-ai/miru-code"]
9
- env = { TAKARA_API_KEY = "\${TAKARA_API_KEY}" }
10
9
  `;
11
10
 
12
11
  /** Strip line and block comments so JSONC configs can be parsed. */