@takara-ai/miru-code 0.4.4 → 0.4.6
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 +6 -4
- package/package.json +1 -1
- package/src/cache.ts +4 -3
- package/src/chunking/structural.ts +35 -1
- package/src/cli.ts +5 -6
- package/src/env.ts +2 -1
- package/src/index/files.ts +4 -1
- package/src/installer/agents.ts +67 -22
- package/src/installer/config.ts +0 -1
- package/src/utils.ts +5 -0
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ miru setup --key YOUR_TOKEN # non-interactive
|
|
|
25
25
|
miru setup --clear # remove stored key
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
MCP loads your key from `credentials.json` automatically
|
|
28
|
+
MCP loads your key from `credentials.json` automatically — no env block needed in MCP config.
|
|
29
29
|
|
|
30
30
|
## Add to your IDE
|
|
31
31
|
|
|
@@ -39,7 +39,7 @@ Interactive — pick your agent and what to enable (MCP, instructions, sub-agent
|
|
|
39
39
|
miru uninstall # remove miru config
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
**Supported:** Cursor · Claude Code · Gemini CLI · Kiro · OpenCode · GitHub Copilot · Codex · VS Code
|
|
42
|
+
**Supported:** Cursor · Claude Code · Gemini CLI · Kiro · OpenCode · GitHub Copilot · Codex · VS Code · Visual Studio (Windows)
|
|
43
43
|
|
|
44
44
|
| IDE | Config written by `miru install` |
|
|
45
45
|
|-----|----------------------------------|
|
|
@@ -50,6 +50,7 @@ miru uninstall # remove miru config
|
|
|
50
50
|
| OpenCode | `~/.config/opencode/opencode.json(c)` + agents dir |
|
|
51
51
|
| GitHub Copilot | `~/.copilot/mcp-config.json` + `~/.copilot/agents/miru-code.agent.md` |
|
|
52
52
|
| Codex / VS Code | MCP config only |
|
|
53
|
+
| Visual Studio | `%USERPROFILE%\.mcp.json` (Windows, Copilot Agent Mode) |
|
|
53
54
|
|
|
54
55
|
**Team sub-agent in a repo** (optional):
|
|
55
56
|
|
|
@@ -132,12 +133,13 @@ See `.env.example` for more.
|
|
|
132
133
|
{
|
|
133
134
|
"miru": {
|
|
134
135
|
"command": "miru",
|
|
135
|
-
"args": []
|
|
136
|
-
"env": { "TAKARA_API_KEY": "your-token" }
|
|
136
|
+
"args": []
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
139
|
```
|
|
140
140
|
|
|
141
|
+
Run `miru setup` once so the server can load your key from `credentials.json`.
|
|
142
|
+
|
|
141
143
|
Use `bunx` + `@takara-ai/miru-code` if `miru` is not global. Wrapper key varies by IDE (`mcpServers`, `servers`, or `mcp`).
|
|
142
144
|
|
|
143
145
|
## Developing
|
package/package.json
CHANGED
package/src/cache.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { rm } from "node:fs/promises";
|
|
3
|
-
import { join
|
|
3
|
+
import { join } from "node:path";
|
|
4
4
|
import { resolveEmbeddingDimensions, resolveEmbeddingModel } from "./embeddings/openai.ts";
|
|
5
5
|
import {
|
|
6
6
|
loadBm25,
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
import { resolveSemanticStorage, semanticStorageFromMetadata } from "./index/vector-storage.ts";
|
|
13
13
|
import type { Chunk, ContentType } from "./types.ts";
|
|
14
14
|
import { chunkFromDict } from "./types.ts";
|
|
15
|
+
import { computeSourceCacheKey } from "./utils.ts";
|
|
15
16
|
|
|
16
17
|
export function resolveCacheFolder(): string {
|
|
17
18
|
const name = "miru";
|
|
@@ -30,8 +31,8 @@ export function resolveCacheFolder(): string {
|
|
|
30
31
|
return base;
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
export function findIndexCachePath(path: string): string {
|
|
34
|
-
const normalized =
|
|
34
|
+
export function findIndexCachePath(path: string, ref?: string | null): string {
|
|
35
|
+
const normalized = computeSourceCacheKey(path, ref);
|
|
35
36
|
const subdir = createHash("sha256").update(normalized, "utf-8").digest("hex");
|
|
36
37
|
return join(resolveCacheFolder(), subdir, "index");
|
|
37
38
|
}
|
|
@@ -5,7 +5,16 @@ import {
|
|
|
5
5
|
splitLinesKeepEnds,
|
|
6
6
|
} from "./lines.ts";
|
|
7
7
|
|
|
8
|
-
const SUPPORTED_STRUCTURAL_LANGUAGES = new Set([
|
|
8
|
+
const SUPPORTED_STRUCTURAL_LANGUAGES = new Set([
|
|
9
|
+
"python",
|
|
10
|
+
"go",
|
|
11
|
+
"typescript",
|
|
12
|
+
"javascript",
|
|
13
|
+
"cpp",
|
|
14
|
+
"c",
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
const BRACE_FAMILY_LANGUAGES = new Set(["cpp", "c"]);
|
|
9
18
|
|
|
10
19
|
export function chunkStructural(
|
|
11
20
|
source: string,
|
|
@@ -164,9 +173,27 @@ function declPattern(language: string): RegExp {
|
|
|
164
173
|
if (language === "go") {
|
|
165
174
|
return /^\s*(func\b|type\b.*\b(struct|interface)\b)/;
|
|
166
175
|
}
|
|
176
|
+
if (language === "cpp") {
|
|
177
|
+
return /^\s*(?:(?:template\s*<[^>]*>\s*)?(?:class|struct|namespace|enum(?:\s+class)?)\b|(?!(?:if|for|while|switch|catch|return|sizeof|static_assert|else|do)\b)(?:[\w:~<>,*&]+\s+)+\w+\s*\([^;{}]*\)\s*(?:const)?\s*(?:override)?(?:\s*=\s*0)?)/;
|
|
178
|
+
}
|
|
179
|
+
if (language === "c") {
|
|
180
|
+
return /^\s*(?:struct|enum|union|typedef)\b|(?!(?:if|for|while|switch|return|sizeof|else|do)\b)(?:[\w:*\s]+\s+)+\w+\s*\([^;{}]*\)/;
|
|
181
|
+
}
|
|
167
182
|
return /^\s*(export\s+)?(async\s+function\b|function\b|class\b|interface\b|type\b|(?:const|let|var)\s+[A-Za-z_$][\w$]*\s*=\s*(?:async\s*)?(?:\([^)]*\)|[A-Za-z_$][\w$]*)(?:\s*:\s*[^=]+)?\s*=>)/;
|
|
168
183
|
}
|
|
169
184
|
|
|
185
|
+
function isBraceFamilyTypeDecl(text: string, language: string): boolean {
|
|
186
|
+
if (language === "cpp") {
|
|
187
|
+
return /^\s*(?:(?:template\s*<[^>]*>\s*)?(?:class|struct|namespace|enum(?:\s+class)?)\b)/.test(
|
|
188
|
+
text,
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
if (language === "c") {
|
|
192
|
+
return /^\s*(?:struct|enum|union)\b/.test(text);
|
|
193
|
+
}
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
|
|
170
197
|
function braceUnits(lines: LineGroup[], language: string): ChunkBoundary[] {
|
|
171
198
|
const units: ChunkBoundary[] = [];
|
|
172
199
|
const full = lines.map((l) => l.text).join("");
|
|
@@ -178,6 +205,13 @@ function braceUnits(lines: LineGroup[], language: string): ChunkBoundary[] {
|
|
|
178
205
|
if (!re.test(text)) {
|
|
179
206
|
continue;
|
|
180
207
|
}
|
|
208
|
+
if (
|
|
209
|
+
BRACE_FAMILY_LANGUAGES.has(language) &&
|
|
210
|
+
lineIndent(text) > 0 &&
|
|
211
|
+
!isBraceFamilyTypeDecl(text, language)
|
|
212
|
+
) {
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
181
215
|
if (firstDeclStart === null) {
|
|
182
216
|
firstDeclStart = lines[i]?.start;
|
|
183
217
|
}
|
package/src/cli.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
-
import { resolve } from "node:path";
|
|
3
2
|
import { type AgentId, writeAgentFile } from "./agents.ts";
|
|
4
3
|
import { clearCache } from "./cache.ts";
|
|
5
4
|
import {
|
|
@@ -12,8 +11,8 @@ import {
|
|
|
12
11
|
success,
|
|
13
12
|
} from "./cli-ui.ts";
|
|
14
13
|
import { loadStoredCredentials } from "./credentials.ts";
|
|
15
|
-
import { loadEnvFiles } from "./env-files.ts";
|
|
16
14
|
import { normalizeTakaraApiKeyEnv } from "./env.ts";
|
|
15
|
+
import { loadEnvFiles } from "./env-files.ts";
|
|
17
16
|
import {
|
|
18
17
|
AGENT_IDS,
|
|
19
18
|
formatUnknownAgent,
|
|
@@ -27,7 +26,7 @@ import { MiruIndex } from "./miru-index.ts";
|
|
|
27
26
|
import { ensureCredentials, runClearCredentials, runSetup } from "./setup.ts";
|
|
28
27
|
import { withSpinner } from "./spinner.ts";
|
|
29
28
|
import type { ContentType, SearchResult } from "./types.ts";
|
|
30
|
-
import { formatResults, resolveChunk, resolveContent } from "./utils.ts";
|
|
29
|
+
import { formatResults, resolveChunk, resolveContent, resolveSearchPath } from "./utils.ts";
|
|
31
30
|
|
|
32
31
|
loadEnvFiles();
|
|
33
32
|
normalizeTakaraApiKeyEnv();
|
|
@@ -283,7 +282,7 @@ async function runCli(argv: string[]): Promise<void> {
|
|
|
283
282
|
}
|
|
284
283
|
|
|
285
284
|
if (command === "clear") {
|
|
286
|
-
const path =
|
|
285
|
+
const path = resolveSearchPath(rest[0] ?? process.cwd());
|
|
287
286
|
await runClear(path);
|
|
288
287
|
return;
|
|
289
288
|
}
|
|
@@ -298,7 +297,7 @@ async function runCli(argv: string[]): Promise<void> {
|
|
|
298
297
|
printCommandHelp("search");
|
|
299
298
|
process.exit(1);
|
|
300
299
|
}
|
|
301
|
-
const path =
|
|
300
|
+
const path = resolveSearchPath(sizedRest[1] ?? process.cwd());
|
|
302
301
|
await runSearch(path, query, topK, content, jsonFlag);
|
|
303
302
|
return;
|
|
304
303
|
}
|
|
@@ -311,7 +310,7 @@ async function runCli(argv: string[]): Promise<void> {
|
|
|
311
310
|
process.exit(1);
|
|
312
311
|
}
|
|
313
312
|
const line = Number(lineRaw);
|
|
314
|
-
const path =
|
|
313
|
+
const path = resolveSearchPath(sizedRest[2] ?? process.cwd());
|
|
315
314
|
try {
|
|
316
315
|
await runFindRelated(path, filePath, line, topK, content, jsonFlag);
|
|
317
316
|
} catch (err) {
|
package/src/env.ts
CHANGED
|
@@ -33,7 +33,8 @@ export function envOptionalInt(names: string[], min = 1): number | undefined {
|
|
|
33
33
|
|
|
34
34
|
export const TAKARA_API_KEY_ENV = "TAKARA_API_KEY";
|
|
35
35
|
|
|
36
|
-
const
|
|
36
|
+
const MCP_KEY_PLACEHOLDER = "$" + "{TAKARA_API_KEY}";
|
|
37
|
+
const TAKARA_API_KEY_PLACEHOLDERS = new Set([MCP_KEY_PLACEHOLDER, "$TAKARA_API_KEY"]);
|
|
37
38
|
|
|
38
39
|
/** True when the value is a real key, not empty or an unexpanded MCP env placeholder. */
|
|
39
40
|
export function isUsableTakaraApiKey(value: string | undefined): boolean {
|
package/src/index/files.ts
CHANGED
|
@@ -21,11 +21,14 @@ const EXTENSION_TO_LANGUAGE: Record<string, string> = {
|
|
|
21
21
|
".kt": "kotlin",
|
|
22
22
|
".kts": "kotlin",
|
|
23
23
|
".c": "c",
|
|
24
|
-
".h": "
|
|
24
|
+
".h": "cpp",
|
|
25
25
|
".cpp": "cpp",
|
|
26
26
|
".cc": "cpp",
|
|
27
27
|
".cxx": "cpp",
|
|
28
28
|
".hpp": "cpp",
|
|
29
|
+
".hh": "cpp",
|
|
30
|
+
".hxx": "cpp",
|
|
31
|
+
".h++": "cpp",
|
|
29
32
|
".cs": "csharp",
|
|
30
33
|
".rb": "ruby",
|
|
31
34
|
".php": "php",
|
package/src/installer/agents.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
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";
|
|
@@ -19,30 +20,21 @@ export const MIRU_END = "<!-- miru:end -->";
|
|
|
19
20
|
|
|
20
21
|
const HOME = homedir();
|
|
21
22
|
|
|
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
23
|
const STDIO_SERVER_CONFIG: Record<string, unknown> = {
|
|
29
24
|
command: "bunx",
|
|
30
25
|
args: ["@takara-ai/miru-code"],
|
|
31
26
|
type: "stdio",
|
|
32
|
-
env: MCP_ENV,
|
|
33
27
|
};
|
|
34
28
|
|
|
35
29
|
const BARE_STDIO_SERVER_CONFIG: Record<string, unknown> = {
|
|
36
30
|
command: "bunx",
|
|
37
31
|
args: ["@takara-ai/miru-code"],
|
|
38
|
-
env: MCP_ENV,
|
|
39
32
|
};
|
|
40
33
|
|
|
41
34
|
const OPENCODE_SERVER_CONFIG: Record<string, unknown> = {
|
|
42
35
|
command: ["bunx", "@takara-ai/miru-code"],
|
|
43
36
|
type: "local",
|
|
44
37
|
enabled: true,
|
|
45
|
-
environment: MCP_ENV,
|
|
46
38
|
};
|
|
47
39
|
|
|
48
40
|
export const INSTRUCTIONS = `${MIRU_START}
|
|
@@ -54,7 +46,7 @@ A \`miru\` MCP server is available with two tools:
|
|
|
54
46
|
|
|
55
47
|
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
48
|
|
|
57
|
-
Run \`miru setup\` once to store your API key — the MCP server loads it from \`credentials.json
|
|
49
|
+
Run \`miru setup\` once to store your API key — the MCP server loads it from \`credentials.json\` automatically.
|
|
58
50
|
|
|
59
51
|
Pass \`--content docs\` to search documentation, \`--content config\` for config files, or \`--content all\` for everything.
|
|
60
52
|
|
|
@@ -92,7 +84,7 @@ export interface McpConfig {
|
|
|
92
84
|
}
|
|
93
85
|
|
|
94
86
|
export interface AgentTarget {
|
|
95
|
-
id: AgentId | "codex" | "vscode";
|
|
87
|
+
id: AgentId | "codex" | "vscode" | "visualstudio";
|
|
96
88
|
displayName: string;
|
|
97
89
|
binary: string | null;
|
|
98
90
|
configDir: string | null;
|
|
@@ -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
|
}
|
|
@@ -171,7 +202,6 @@ export const AGENT_TARGETS: AgentTarget[] = [
|
|
|
171
202
|
mcp: jsonMcp(join(HOME, ".kiro", "settings", "mcp.json"), "mcpServers", {
|
|
172
203
|
...STDIO_SERVER_CONFIG,
|
|
173
204
|
env: {
|
|
174
|
-
...MCP_ENV,
|
|
175
205
|
PATH: "/usr/local/bin:/usr/bin:/bin",
|
|
176
206
|
},
|
|
177
207
|
}),
|
|
@@ -225,19 +255,34 @@ export const AGENT_TARGETS: AgentTarget[] = [
|
|
|
225
255
|
subagentPath: null,
|
|
226
256
|
subagentId: null,
|
|
227
257
|
},
|
|
258
|
+
{
|
|
259
|
+
id: "visualstudio",
|
|
260
|
+
displayName: process.platform === "win32" ? "Visual Studio" : "Visual Studio (Windows)",
|
|
261
|
+
binary: null,
|
|
262
|
+
configDir: visualStudioInstallDir(),
|
|
263
|
+
mcp: jsonMcp(visualStudioMcpPath(), "servers", STDIO_SERVER_CONFIG),
|
|
264
|
+
instructionsPath: null,
|
|
265
|
+
subagentPath: null,
|
|
266
|
+
subagentId: null,
|
|
267
|
+
},
|
|
228
268
|
];
|
|
229
269
|
|
|
270
|
+
async function commandOnPath(command: string): Promise<boolean> {
|
|
271
|
+
const lookup = process.platform === "win32" ? ["where", command] : ["which", command];
|
|
272
|
+
try {
|
|
273
|
+
const proc = Bun.spawn(lookup, { stdout: "pipe", stderr: "ignore" });
|
|
274
|
+
return (await proc.exited) === 0;
|
|
275
|
+
} catch {
|
|
276
|
+
return false;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
230
280
|
export async function isAgentDetected(agent: AgentTarget): Promise<boolean> {
|
|
231
|
-
if (agent.
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
return true;
|
|
237
|
-
}
|
|
238
|
-
} catch {
|
|
239
|
-
// which unavailable
|
|
240
|
-
}
|
|
281
|
+
if (agent.id === "visualstudio") {
|
|
282
|
+
return detectVisualStudio();
|
|
283
|
+
}
|
|
284
|
+
if (agent.binary && (await commandOnPath(agent.binary))) {
|
|
285
|
+
return true;
|
|
241
286
|
}
|
|
242
287
|
if (agent.configDir) {
|
|
243
288
|
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. */
|
package/src/utils.ts
CHANGED
|
@@ -62,3 +62,8 @@ export function computeSourceCacheKey(source: string, ref?: string | null): stri
|
|
|
62
62
|
}
|
|
63
63
|
return resolve(source);
|
|
64
64
|
}
|
|
65
|
+
|
|
66
|
+
/** Resolve a local search path; leave git URLs unchanged for remote indexing. */
|
|
67
|
+
export function resolveSearchPath(path: string): string {
|
|
68
|
+
return isGitUrl(path) ? path : resolve(path);
|
|
69
|
+
}
|