@takara-ai/miru-code 0.4.3 → 0.4.5

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
@@ -25,7 +25,7 @@ miru setup --key YOUR_TOKEN # non-interactive
25
25
  miru setup --clear # remove stored key
26
26
  ```
27
27
 
28
- For MCP in an IDE, export `TAKARA_API_KEY` or paste your token into the MCP `env` block after install.
28
+ MCP loads your key from `credentials.json` automatically. Set `TAKARA_API_KEY` in MCP config only to override.
29
29
 
30
30
  ## Add to your IDE
31
31
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@takara-ai/miru-code",
3
- "version": "0.4.3",
3
+ "version": "0.4.5",
4
4
  "description": "Miru (見る) — hybrid code search for agents. Bun, TypeScript, Takara embeddings.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/cache.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { rm } from "node:fs/promises";
3
- import { join, resolve } from "node:path";
3
+ import { join } from "node:path";
4
+ import { computeSourceCacheKey } from "./utils.ts";
4
5
  import { resolveEmbeddingDimensions, resolveEmbeddingModel } from "./embeddings/openai.ts";
5
6
  import {
6
7
  loadBm25,
@@ -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 = resolve(path);
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(["python", "go", "typescript", "javascript"]);
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 {
@@ -13,6 +12,7 @@ import {
13
12
  } from "./cli-ui.ts";
14
13
  import { loadStoredCredentials } from "./credentials.ts";
15
14
  import { loadEnvFiles } from "./env-files.ts";
15
+ import { normalizeTakaraApiKeyEnv } from "./env.ts";
16
16
  import {
17
17
  AGENT_IDS,
18
18
  formatUnknownAgent,
@@ -26,9 +26,10 @@ import { MiruIndex } from "./miru-index.ts";
26
26
  import { ensureCredentials, runClearCredentials, runSetup } from "./setup.ts";
27
27
  import { withSpinner } from "./spinner.ts";
28
28
  import type { ContentType, SearchResult } from "./types.ts";
29
- import { formatResults, resolveChunk, resolveContent } from "./utils.ts";
29
+ import { formatResults, resolveChunk, resolveContent, resolveSearchPath } from "./utils.ts";
30
30
 
31
31
  loadEnvFiles();
32
+ normalizeTakaraApiKeyEnv();
32
33
  await loadStoredCredentials();
33
34
 
34
35
  const CLI_COMMANDS = new Set([
@@ -281,7 +282,7 @@ async function runCli(argv: string[]): Promise<void> {
281
282
  }
282
283
 
283
284
  if (command === "clear") {
284
- const path = resolve(rest[0] ?? process.cwd());
285
+ const path = resolveSearchPath(rest[0] ?? process.cwd());
285
286
  await runClear(path);
286
287
  return;
287
288
  }
@@ -296,7 +297,7 @@ async function runCli(argv: string[]): Promise<void> {
296
297
  printCommandHelp("search");
297
298
  process.exit(1);
298
299
  }
299
- const path = resolve(sizedRest[1] ?? process.cwd());
300
+ const path = resolveSearchPath(sizedRest[1] ?? process.cwd());
300
301
  await runSearch(path, query, topK, content, jsonFlag);
301
302
  return;
302
303
  }
@@ -309,7 +310,7 @@ async function runCli(argv: string[]): Promise<void> {
309
310
  process.exit(1);
310
311
  }
311
312
  const line = Number(lineRaw);
312
- const path = resolve(sizedRest[2] ?? process.cwd());
313
+ const path = resolveSearchPath(sizedRest[2] ?? process.cwd());
313
314
  try {
314
315
  await runFindRelated(path, filePath, line, topK, content, jsonFlag);
315
316
  } catch (err) {
@@ -1,6 +1,6 @@
1
1
  import { chmod, mkdir, readFile, rm, writeFile } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
- import { hasTakaraApiKeyInEnv } from "./env.ts";
3
+ import { hasTakaraApiKeyInEnv, normalizeTakaraApiKeyEnv } from "./env.ts";
4
4
 
5
5
  const CREDENTIALS_FILENAME = "credentials.json";
6
6
  const CREDENTIALS_VERSION = 1;
@@ -49,6 +49,7 @@ export async function readStoredCredentials(): Promise<StoredCredentials | null>
49
49
 
50
50
  /** Set TAKARA_API_KEY from the user credentials file when env is unset. */
51
51
  export async function loadStoredCredentials(): Promise<boolean> {
52
+ normalizeTakaraApiKeyEnv();
52
53
  if (hasTakaraApiKeyInEnv()) {
53
54
  return false;
54
55
  }
package/src/env.ts CHANGED
@@ -33,13 +33,31 @@ 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 TAKARA_API_KEY_PLACEHOLDERS = new Set(["${TAKARA_API_KEY}", "$TAKARA_API_KEY"]);
37
+
38
+ /** True when the value is a real key, not empty or an unexpanded MCP env placeholder. */
39
+ export function isUsableTakaraApiKey(value: string | undefined): boolean {
40
+ const key = value?.trim() ?? "";
41
+ if (!key) {
42
+ return false;
43
+ }
44
+ return !TAKARA_API_KEY_PLACEHOLDERS.has(key);
45
+ }
46
+
47
+ /** Drop placeholder values so credentials.json can supply the key for MCP. */
48
+ export function normalizeTakaraApiKeyEnv(): void {
49
+ if (!isUsableTakaraApiKey(process.env[TAKARA_API_KEY_ENV])) {
50
+ delete process.env[TAKARA_API_KEY_ENV];
51
+ }
52
+ }
53
+
36
54
  export function hasTakaraApiKeyInEnv(): boolean {
37
- return Boolean(process.env[TAKARA_API_KEY_ENV]?.trim());
55
+ return isUsableTakaraApiKey(process.env[TAKARA_API_KEY_ENV]);
38
56
  }
39
57
 
40
58
  export function resolveEmbeddingApiKey(): string {
41
59
  const key = process.env[TAKARA_API_KEY_ENV]?.trim() ?? "";
42
- if (!key) {
60
+ if (!isUsableTakaraApiKey(key)) {
43
61
  throw new Error(
44
62
  "Takara API key required. Run `miru setup`, or set TAKARA_API_KEY in your MCP server env or .env.local.",
45
63
  );
@@ -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": "c",
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",
@@ -54,7 +54,7 @@ A \`miru\` MCP server is available with two tools:
54
54
 
55
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
56
 
57
- Set \`TAKARA_API_KEY\` in your MCP config (or run \`miru setup\` for CLI use). MCP does not inherit shell env reliably.
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
58
 
59
59
  Pass \`--content docs\` to search documentation, \`--content config\` for config files, or \`--content all\` for everything.
60
60
 
@@ -246,7 +246,7 @@ export async function runInstaller(mode: InstallMode): Promise<void> {
246
246
  install ? "Done! Restart your agents to pick up changes." : "Done! Configuration removed.",
247
247
  );
248
248
  if (install) {
249
- hint("Run miru setup or export TAKARA_API_KEY for MCP env expansion.");
249
+ hint("Run miru setup — MCP loads the key from credentials.json automatically.");
250
250
  }
251
251
  writeStdout("");
252
252
  }
package/src/setup.ts CHANGED
@@ -73,7 +73,7 @@ export async function runSetup(options: RunSetupOptions = {}): Promise<string> {
73
73
  process.env.TAKARA_API_KEY = apiKey;
74
74
  writeStderr("");
75
75
  success(`Saved credentials to ${path}`);
76
- hint("Set TAKARA_API_KEY in MCP config for IDE integrations.");
76
+ hint("MCP loads this key from credentials.json automatically.");
77
77
  writeStderr("");
78
78
  return path;
79
79
  }
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
+ }