@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.
- package/README.md +38 -15
- package/package.json +1 -1
- package/src/agents/claude.md +16 -28
- package/src/agents/copilot.md +9 -35
- package/src/agents/cursor.md +15 -28
- package/src/agents/gemini.md +10 -36
- package/src/agents/kiro.md +4 -37
- package/src/agents/opencode.md +4 -37
- package/src/cache.ts +1 -1
- package/src/cli.ts +10 -1
- package/src/env.ts +2 -1
- package/src/installer/agents.ts +146 -52
- package/src/installer/config.ts +0 -1
- package/src/installer/hooks/install.ts +463 -0
- package/src/installer/hooks/opencode-plugin.ts +68 -0
- package/src/installer/hooks/search-guard.ts +215 -0
- package/src/installer/installer.ts +56 -3
- package/src/installer/prompt.ts +218 -32
- package/src/installer/search-policy.ts +68 -0
- package/src/mcp/server.ts +8 -9
package/src/installer/agents.ts
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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();
|
package/src/installer/config.ts
CHANGED
|
@@ -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. */
|