@takara-ai/miru-code 1.1.3 → 1.2.1

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/cli.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env bun
2
2
  import { type AgentId, writeAgentFile } from "./agents.ts";
3
+ import { clearBenchmarkHistory, resolveBenchmarkHistoryPath } from "./benchmark/history.ts";
3
4
  import { clearCache } from "./cache.ts";
4
5
  import {
5
6
  fail,
@@ -7,8 +8,10 @@ import {
7
8
  formatSearchErrorPretty,
8
9
  formatSearchResultsPretty,
9
10
  hint,
11
+ info,
10
12
  prefersJsonOutput,
11
13
  success,
14
+ writeStdout,
12
15
  } from "./cli-ui.ts";
13
16
  import { loadStoredCredentials } from "./credentials.ts";
14
17
  import { normalizeTakaraApiKeyEnv } from "./env.ts";
@@ -20,9 +23,11 @@ import {
20
23
  printFullHelp,
21
24
  printMainHelp,
22
25
  } from "./help.ts";
26
+ import { getBenchmarkModeStatus, setBenchmarkMode } from "./installer/benchmark-mode.ts";
23
27
  import { runSearchGuardFromStdin } from "./installer/hooks/search-guard.ts";
24
28
  import { runInstaller } from "./installer/installer.ts";
25
29
  import { promptConfirm } from "./installer/prompt.ts";
30
+ import { formatLiteralLocate, type LiteralMode } from "./literal.ts";
26
31
  import { serveMcp } from "./mcp/serve.ts";
27
32
  import { MiruIndex } from "./miru-index.ts";
28
33
  import {
@@ -34,6 +39,8 @@ import {
34
39
  import { withSpinner } from "./spinner.ts";
35
40
  import type { ContentType, SearchResult } from "./types.ts";
36
41
  import {
42
+ DEFAULT_EXPAND_AFTER,
43
+ DEFAULT_EXPAND_BEFORE,
37
44
  expandChunksAtLine,
38
45
  formatExpandResults,
39
46
  formatResults,
@@ -52,6 +59,7 @@ await loadStoredCredentials();
52
59
 
53
60
  const CLI_COMMANDS = new Set([
54
61
  "search",
62
+ "locate",
55
63
  "expand",
56
64
  "find-related",
57
65
  "init",
@@ -59,6 +67,7 @@ const CLI_COMMANDS = new Set([
59
67
  "uninstall",
60
68
  "setup",
61
69
  "clear",
70
+ "benchmark",
62
71
  "hook-guard",
63
72
  "help",
64
73
  "-h",
@@ -104,7 +113,7 @@ function parseContentArgv(argv: string[]): { content: ContentType[]; rest: strin
104
113
  rest.push(arg);
105
114
  }
106
115
  }
107
- return { content: resolveContent(content.length > 0 ? content : ["code"]), rest };
116
+ return { content: resolveContent(content), rest };
108
117
  }
109
118
 
110
119
  function parseTopK(argv: string[]): { topK: number; rest: string[] } {
@@ -250,6 +259,53 @@ class RelatedChunkNotFoundError extends Error {
250
259
  }
251
260
  }
252
261
 
262
+ async function runLocate(
263
+ path: string,
264
+ literal: string,
265
+ content: ContentType[],
266
+ jsonFlag: boolean,
267
+ mode: LiteralMode,
268
+ limit: number | undefined,
269
+ ignoreCase: boolean,
270
+ ): Promise<void> {
271
+ await ensureCredentials({ interactive: true });
272
+
273
+ const payload = await withSpinner("Locating literal", async () => {
274
+ const built = await MiruIndex.fromSource(path, content);
275
+ await built.saveToCache(path);
276
+ return formatLiteralLocate(
277
+ built.locateLiteral(literal, {
278
+ mode,
279
+ ignore_case: ignoreCase,
280
+ ...(limit != null ? { limit } : {}),
281
+ }),
282
+ );
283
+ });
284
+
285
+ if (prefersJsonOutput(jsonFlag)) {
286
+ console.log(JSON.stringify(payload));
287
+ return;
288
+ }
289
+
290
+ const n = Number(payload.n ?? 0);
291
+ const files = Number(payload.files ?? 0);
292
+ writeStdout(`literal=${literal} n=${n} files=${files} mode=${mode}`);
293
+ const hits = payload.hits as Array<{ f: string; l: number; t?: string }> | undefined;
294
+ if (hits) {
295
+ for (const hit of hits) {
296
+ if (hit.t !== undefined) {
297
+ writeStdout(` ${hit.f}:${hit.l}: ${hit.t}`);
298
+ } else {
299
+ writeStdout(` ${hit.f}:${hit.l}`);
300
+ }
301
+ }
302
+ if (payload.truncated) {
303
+ writeStdout(` … truncated (showing ${hits.length} of ${n})`);
304
+ }
305
+ }
306
+ writeStdout("");
307
+ }
308
+
253
309
  async function runInit(agent: AgentId, force: boolean): Promise<void> {
254
310
  try {
255
311
  const dest = await writeAgentFile(agent, { force });
@@ -267,6 +323,75 @@ async function runClear(path: string): Promise<void> {
267
323
  success(`Cleared cached index for ${path}`);
268
324
  }
269
325
 
326
+ async function runBenchmarkCommand(rest: string[]): Promise<void> {
327
+ const action = rest[0];
328
+ if (!action || action === "-h" || action === "--help") {
329
+ printCommandHelp("benchmark");
330
+ return;
331
+ }
332
+
333
+ if (action === "status") {
334
+ const rows = await getBenchmarkModeStatus();
335
+ const installed = rows.filter((row) => row.action !== "missing");
336
+ if (installed.length === 0) {
337
+ info("No Miru MCP installs found. Run `miru install` first.");
338
+ return;
339
+ }
340
+ for (const row of installed) {
341
+ writeStdout(` ${row.enabled ? "on " : "off"} ${row.agent.padEnd(16)} ${row.path}`);
342
+ }
343
+ const onCount = installed.filter((row) => row.enabled).length;
344
+ writeStdout("");
345
+ if (onCount > 0) {
346
+ hint(
347
+ `Benchmark mode on for ${onCount}/${installed.length}. Run \`miru benchmark off\` to leave.`,
348
+ );
349
+ } else {
350
+ hint("Benchmark mode is off for all installed agents.");
351
+ }
352
+ return;
353
+ }
354
+
355
+ if (action === "on" || action === "off") {
356
+ const enabled = action === "on";
357
+ const rows = await setBenchmarkMode(enabled);
358
+ const touched = rows.filter((row) => row.action === "updated" || row.action === "unchanged");
359
+ if (touched.length === 0) {
360
+ info("No Miru MCP installs found. Run `miru install` first.");
361
+ return;
362
+ }
363
+ for (const row of touched) {
364
+ const label = row.action === "updated" ? (enabled ? "enabled" : "disabled") : "unchanged";
365
+ writeStdout(` ${label.padEnd(9)} ${row.agent.padEnd(16)} ${row.path}`);
366
+ }
367
+ writeStdout("");
368
+ success(
369
+ enabled
370
+ ? "Benchmark mode on. Restart agents to apply."
371
+ : "Benchmark mode off. Restart agents to apply.",
372
+ );
373
+ if (enabled) {
374
+ hint("Leave anytime with `miru benchmark off`.");
375
+ }
376
+ return;
377
+ }
378
+
379
+ if (action === "clear") {
380
+ const path = resolveBenchmarkHistoryPath();
381
+ const result = await clearBenchmarkHistory(path);
382
+ if (result.cleared) {
383
+ success(`Cleared benchmark report ${result.path}`);
384
+ } else {
385
+ info(`No benchmark report at ${result.path}`);
386
+ }
387
+ return;
388
+ }
389
+
390
+ fail(`Unknown benchmark action "${action}". Use on, off, status, or clear.`);
391
+ printCommandHelp("benchmark");
392
+ process.exit(1);
393
+ }
394
+
270
395
  async function runCli(argv: string[]): Promise<void> {
271
396
  const [command, ...rest] = argv;
272
397
 
@@ -304,6 +429,11 @@ async function runCli(argv: string[]): Promise<void> {
304
429
  return;
305
430
  }
306
431
 
432
+ if (command === "benchmark") {
433
+ await runBenchmarkCommand(rest);
434
+ return;
435
+ }
436
+
307
437
  if (command === "init") {
308
438
  let agent: AgentId | undefined;
309
439
  let force = false;
@@ -392,6 +522,49 @@ async function runCli(argv: string[]): Promise<void> {
392
522
  return;
393
523
  }
394
524
 
525
+ if (command === "locate") {
526
+ const literal = sizedRest[0];
527
+ if (!literal) {
528
+ printCommandHelp("locate");
529
+ process.exit(1);
530
+ }
531
+ let mode: LiteralMode = "lines";
532
+ let limit: number | undefined;
533
+ let ignoreCase = false;
534
+ const pathArgs: string[] = [];
535
+ for (let i = 1; i < sizedRest.length; i++) {
536
+ const arg = sizedRest[i];
537
+ if (arg === "--mode" && sizedRest[i + 1]) {
538
+ const value = sizedRest[++i];
539
+ if (value !== "count" && value !== "locations" && value !== "lines") {
540
+ fail(`Unknown locate mode "${value}". Use count, locations, or lines.`);
541
+ process.exit(1);
542
+ }
543
+ mode = value;
544
+ continue;
545
+ }
546
+ if (arg === "--limit" && sizedRest[i + 1]) {
547
+ const raw = Number(sizedRest[++i]);
548
+ if (!Number.isFinite(raw) || raw < 1) {
549
+ fail("locate --limit must be a positive integer.");
550
+ process.exit(1);
551
+ }
552
+ limit = Math.floor(raw);
553
+ continue;
554
+ }
555
+ if (arg === "--ignore-case") {
556
+ ignoreCase = true;
557
+ continue;
558
+ }
559
+ if (arg !== undefined) {
560
+ pathArgs.push(arg);
561
+ }
562
+ }
563
+ const path = resolveSearchPath(pathArgs[0] ?? process.cwd());
564
+ await runLocate(path, literal, content, jsonFlag, mode, limit, ignoreCase);
565
+ return;
566
+ }
567
+
395
568
  if (command === "expand") {
396
569
  const filePath = sizedRest[0];
397
570
  const lineRaw = sizedRest[1];
@@ -401,8 +574,8 @@ async function runCli(argv: string[]): Promise<void> {
401
574
  }
402
575
  const line = Number(lineRaw);
403
576
  const path = resolveSearchPath(sizedRest[2] ?? process.cwd());
404
- let before = 1;
405
- let after = 1;
577
+ let before = DEFAULT_EXPAND_BEFORE;
578
+ let after = DEFAULT_EXPAND_AFTER;
406
579
  for (let i = 3; i < sizedRest.length; i++) {
407
580
  const arg = sizedRest[i];
408
581
  if (arg === "--before" && sizedRest[i + 1]) {
@@ -435,10 +608,15 @@ async function runCli(argv: string[]): Promise<void> {
435
608
 
436
609
  async function runMcp(argv: string[]): Promise<void> {
437
610
  let ref: string | null = null;
611
+ let benchmark = false;
438
612
  const contentTokens: string[] = [];
439
613
 
440
614
  for (let i = 0; i < argv.length; i++) {
441
615
  const arg = argv[i];
616
+ if (arg === "--benchmark") {
617
+ benchmark = true;
618
+ continue;
619
+ }
442
620
  if (arg === "--ref" && argv[i + 1]) {
443
621
  ref = argv[++i] ?? null;
444
622
  continue;
@@ -459,7 +637,8 @@ async function runMcp(argv: string[]): Promise<void> {
459
637
 
460
638
  await serveMcp({
461
639
  ref,
462
- content: resolveContent(contentTokens.length > 0 ? contentTokens : ["code"]),
640
+ content: resolveContent(contentTokens),
641
+ benchmark,
463
642
  });
464
643
  }
465
644
 
@@ -27,6 +27,14 @@ export function resolveCredentialsDir(): string {
27
27
  return join(xdg, "miru");
28
28
  }
29
29
 
30
+ /**
31
+ * Global Miru state directory (credentials, benchmark history, etc.).
32
+ * Same location as `resolveCredentialsDir()`; prefer this name for non-secret files.
33
+ */
34
+ export function resolveMiruStateDir(): string {
35
+ return resolveCredentialsDir();
36
+ }
37
+
30
38
  export function resolveCredentialsPath(): string {
31
39
  return join(resolveCredentialsDir(), CREDENTIALS_FILENAME);
32
40
  }
package/src/help.ts CHANGED
@@ -34,11 +34,13 @@ export function printMainHelp(): void {
34
34
 
35
35
  section("Commands");
36
36
  commandRow("search", "Hybrid search over a codebase");
37
+ commandRow("locate", "Exact substring location in the index");
37
38
  commandRow("expand", "Adjacent chunks in the same file as a hit");
38
39
  commandRow("find-related", "Find chunks related to a file:line");
39
40
  commandRow("setup", "Save your Takara API key locally");
40
41
  commandRow("install", "Configure miru across coding agents");
41
42
  commandRow("uninstall", "Remove miru agent configuration");
43
+ commandRow("benchmark", "Turn MCP benchmark mode on or off");
42
44
  commandRow("init", "Write a project-local sub-agent file");
43
45
  commandRow("clear", "Remove cached index for a path");
44
46
  commandRow("help", "Show help for a command");
@@ -62,6 +64,8 @@ export function printEnvHelp(): void {
62
64
  writeStdout(` Default: ${DEFAULT_EMBEDDING_MODEL}`);
63
65
  writeStdout(" MIRU_CONCURRENCY");
64
66
  writeStdout(` Parallel workers (default: CPUs − ${DEFAULT_RESERVE_CORES})`);
67
+ writeStdout(" MIRU_TOKENIZER_JSON");
68
+ writeStdout(" Path to tokenizer.json (default: <package>/tokenizer/tokenizer.json)");
65
69
  writeStdout("");
66
70
  }
67
71
 
@@ -78,12 +82,26 @@ export function printCommandHelp(command: string): void {
78
82
  writeStdout(" miru search <query> [path] [options]");
79
83
  section("Options");
80
84
  writeStdout(" -k, --top-k N Number of results (default: 5)");
81
- writeStdout(" --content TYPE code | docs | config | all");
85
+ writeStdout(" --content TYPE code | docs | config | all (default: code config)");
82
86
  writeStdout(" --json JSON output (default when piped)");
83
87
  section("Example");
84
88
  writeStdout(' miru search "where is auth" ./src -k 10 --content code docs');
85
89
  writeStdout("");
86
90
  return;
91
+ case "locate":
92
+ commandHeader("locate", "Exact substring location over the Miru index.");
93
+ section("Usage");
94
+ writeStdout(" miru locate <literal> [path] [options]");
95
+ section("Options");
96
+ writeStdout(" --mode MODE count | locations | lines (default: lines)");
97
+ writeStdout(" --limit N Optional hit cap (default: all matches)");
98
+ writeStdout(" --ignore-case Case-insensitive match");
99
+ writeStdout(" --content TYPE code | docs | config | all (default: code config)");
100
+ writeStdout(" --json JSON output (default when piped)");
101
+ section("Example");
102
+ writeStdout(" miru locate MIRU_BENCHMARK_HISTORY_PATH . --mode locations");
103
+ writeStdout("");
104
+ return;
87
105
  case "expand":
88
106
  commandHeader("expand", "More context in the same file as a search hit.");
89
107
  section("Usage");
@@ -121,6 +139,28 @@ export function printCommandHelp(command: string): void {
121
139
  case "uninstall":
122
140
  commandHeader("uninstall", "Remove miru configuration from agents.");
123
141
  writeStdout("Removes MCP entries, marked instruction blocks, and global sub-agents.");
142
+ writeStdout("Also deletes the global benchmark report from Miru's state directory.");
143
+ writeStdout("");
144
+ return;
145
+ case "benchmark":
146
+ commandHeader("benchmark", "Toggle MCP benchmark mode on installed agents.");
147
+ section("Usage");
148
+ writeStdout(" miru benchmark on");
149
+ writeStdout(" miru benchmark off");
150
+ writeStdout(" miru benchmark status");
151
+ writeStdout(" miru benchmark clear");
152
+ writeStdout("");
153
+ writeStdout("Adds or removes `--benchmark` from Miru MCP args in agent configs.");
154
+ writeStdout("If `--benchmark` is present, benchmark mode is on — no env overrides.");
155
+ writeStdout("Restart agents after changing mode. Prefer `off` when finished measuring.");
156
+ writeStdout("");
157
+ writeStdout("History is a global report (not per-repo) under Miru's state directory.");
158
+ writeStdout("Override path with MIRU_BENCHMARK_HISTORY_PATH. Append-only JSONL of savings.");
159
+ writeStdout("Repo paths in the report are plaintext — use `clear` on shared machines.");
160
+ writeStdout("");
161
+ writeStdout("Savings compare Miru workflow tokens to a Grep baseline");
162
+ writeStdout("(rg search + read of the top matched file), not the agent's full tool chain.");
163
+ writeStdout("Local paths only — git URL repos skip comparison with benchmark_skipped.");
124
164
  writeStdout("");
125
165
  return;
126
166
  case "init":
@@ -145,9 +185,12 @@ export function printCommandHelp(command: string): void {
145
185
  case "mcp":
146
186
  commandHeader("mcp", "Stdio MCP server (default with no subcommand).");
147
187
  section("Usage");
148
- writeStdout(" miru [--ref BRANCH] [--content TYPE ...]");
188
+ writeStdout(" miru [--ref BRANCH] [--content TYPE ...] [--benchmark]");
189
+ writeStdout(" Default content: code config");
149
190
  writeStdout("");
150
191
  writeStdout("Indexes on the first search/expand/find_related tool call (repo argument).");
192
+ writeStdout("Use --benchmark (or `miru benchmark on`) for token-savings comparisons.");
193
+ writeStdout("Leave with `miru benchmark off` and restart the agent.");
151
194
  writeStdout("");
152
195
  return;
153
196
  default:
@@ -6,7 +6,7 @@ import type { EmbeddingBackend } from "../embeddings/openai.ts";
6
6
  import { envOptionalInt } from "../env.ts";
7
7
  import { searchImprovementsEnabled } from "../ranking/features.ts";
8
8
  import { tokenize } from "../tokens.ts";
9
- import type { Chunk, ContentType } from "../types.ts";
9
+ import { type Chunk, type ContentType, defaultContentTypes } from "../types.ts";
10
10
  import { BM25Index } from "./bm25.ts";
11
11
  import { loadRootEntryChunks } from "./entry-chunks.ts";
12
12
  import { walkFiles } from "./file-walker.ts";
@@ -33,7 +33,7 @@ function resolveMaxIndexFiles(): number | undefined {
33
33
  export async function createIndexFromPath(
34
34
  path: string,
35
35
  embeddings: EmbeddingBackend,
36
- content: ContentType[] = ["code"],
36
+ content: ContentType[] = defaultContentTypes(),
37
37
  displayRoot?: string,
38
38
  ): Promise<{ bm25: BM25Index; semantic: SemanticIndex; chunks: Chunk[] }> {
39
39
  const profile = process.env.MIRU_PROFILE === "1";
@@ -1,4 +1,4 @@
1
- import { join, relative, resolve } from "node:path";
1
+ import { isAbsolute, join, relative, resolve } from "node:path";
2
2
  import { chunkSource } from "../chunking/chunking.ts";
3
3
  import type { EmbeddingBackend } from "../embeddings/openai.ts";
4
4
  import { tokenize } from "../tokens.ts";
@@ -123,8 +123,9 @@ function rebuildBm25(chunks: Chunk[]): BM25Index {
123
123
  export function relativePathFromRoot(root: string, absoluteOrRelative: string): string {
124
124
  const resolvedRoot = resolve(root);
125
125
  const candidate = resolve(resolvedRoot, absoluteOrRelative);
126
- if (candidate.startsWith(`${resolvedRoot}/`) || candidate === resolvedRoot) {
127
- return normalizeRelativePath(relative(resolvedRoot, candidate));
126
+ const rel = normalizeRelativePath(relative(resolvedRoot, candidate));
127
+ if (rel === "" || (!rel.startsWith("../") && rel !== ".." && !isAbsolute(rel))) {
128
+ return rel;
128
129
  }
129
130
  return normalizeRelativePath(absoluteOrRelative);
130
131
  }
package/src/index.ts CHANGED
@@ -18,8 +18,10 @@ export { MiruIndex } from "./miru-index.ts";
18
18
  export type {
19
19
  /** A searchable text span from a source file (path, line range, content). */
20
20
  Chunk,
21
- /** What to index: source code, markdown docs, or both. */
21
+ /** What to index: source code, docs, and/or config. */
22
22
  ContentType,
23
23
  /** Ranked hit returned by `search` or `findRelated`. */
24
24
  SearchResult,
25
25
  } from "./types.ts";
26
+
27
+ export { DEFAULT_CONTENT_TYPES, defaultContentTypes } from "./types.ts";
@@ -0,0 +1,248 @@
1
+ import { AGENT_TARGETS, type AgentTarget, type InstallAction } from "./agents.ts";
2
+ import { removeTomlBlock, stripJsonComments } from "./config.ts";
3
+
4
+ export const MCP_BENCHMARK_FLAG = "--benchmark";
5
+
6
+ const CODEX_MCP_HEADER = "[mcp_servers.miru]";
7
+
8
+ function codexMcpBlock(enabled: boolean): string {
9
+ const args = enabled
10
+ ? `["@takara-ai/miru-code", "${MCP_BENCHMARK_FLAG}"]`
11
+ : '["@takara-ai/miru-code"]';
12
+ return `${CODEX_MCP_HEADER}\ncommand = "bunx"\nargs = ${args}\n`;
13
+ }
14
+
15
+ export function withBenchmarkFlag(list: string[], enabled: boolean): string[] {
16
+ const without = list.filter((item) => item !== MCP_BENCHMARK_FLAG);
17
+ return enabled ? [...without, MCP_BENCHMARK_FLAG] : without;
18
+ }
19
+
20
+ export function listHasBenchmarkFlag(list: unknown): boolean {
21
+ return Array.isArray(list) && list.some((item) => item === MCP_BENCHMARK_FLAG);
22
+ }
23
+
24
+ /** Toggle `--benchmark` on a JSON MCP server entry (`args` and/or array `command`). */
25
+ export function applyBenchmarkFlagToMcpEntry(
26
+ entry: Record<string, unknown>,
27
+ enabled: boolean,
28
+ ): { entry: Record<string, unknown>; changed: boolean; enabled: boolean } {
29
+ const next: Record<string, unknown> = { ...entry };
30
+ let changed = false;
31
+
32
+ if (Array.isArray(next.args)) {
33
+ const args = next.args.filter((item): item is string => typeof item === "string");
34
+ const updated = withBenchmarkFlag(args, enabled);
35
+ if (JSON.stringify(updated) !== JSON.stringify(args)) {
36
+ next.args = updated;
37
+ changed = true;
38
+ }
39
+ }
40
+
41
+ if (Array.isArray(next.command)) {
42
+ const command = next.command.filter((item): item is string => typeof item === "string");
43
+ const updated = withBenchmarkFlag(command, enabled);
44
+ if (JSON.stringify(updated) !== JSON.stringify(command)) {
45
+ next.command = updated;
46
+ changed = true;
47
+ }
48
+ }
49
+
50
+ return {
51
+ entry: next,
52
+ changed,
53
+ enabled: mcpEntryHasBenchmark(next),
54
+ };
55
+ }
56
+
57
+ export function mcpEntryHasBenchmark(entry: Record<string, unknown>): boolean {
58
+ return listHasBenchmarkFlag(entry.args) || listHasBenchmarkFlag(entry.command);
59
+ }
60
+
61
+ /**
62
+ * Keep `--benchmark` on the canonical install entry when the existing MCP
63
+ * entry already had it (so `miru install` does not silently turn mode off).
64
+ */
65
+ export function withPreservedBenchmarkFlag(
66
+ canonical: Record<string, unknown>,
67
+ existing: Record<string, unknown> | null | undefined,
68
+ ): Record<string, unknown> {
69
+ if (!existing || !mcpEntryHasBenchmark(existing)) {
70
+ return canonical;
71
+ }
72
+ return applyBenchmarkFlagToMcpEntry(canonical, true).entry;
73
+ }
74
+
75
+ export interface BenchmarkModeTargetResult {
76
+ agent: string;
77
+ path: string;
78
+ action: InstallAction | "enabled" | "disabled" | "missing";
79
+ enabled: boolean;
80
+ }
81
+
82
+ async function readJsonObject(path: string): Promise<Record<string, unknown> | null> {
83
+ if (!(await Bun.file(path).exists())) {
84
+ return null;
85
+ }
86
+ try {
87
+ const text = stripJsonComments(await Bun.file(path).text());
88
+ const parsed = JSON.parse(text) as unknown;
89
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
90
+ return null;
91
+ }
92
+ return parsed as Record<string, unknown>;
93
+ } catch {
94
+ return null;
95
+ }
96
+ }
97
+
98
+ async function writeJsonObject(path: string, value: Record<string, unknown>): Promise<void> {
99
+ await Bun.write(path, `${JSON.stringify(value, null, 2)}\n`);
100
+ }
101
+
102
+ async function statusForToml(path: string): Promise<boolean | null> {
103
+ if (!(await Bun.file(path).exists())) {
104
+ return null;
105
+ }
106
+ const text = await Bun.file(path).text();
107
+ if (!text.includes(CODEX_MCP_HEADER)) {
108
+ return null;
109
+ }
110
+ return text.includes(`"${MCP_BENCHMARK_FLAG}"`) || text.includes(`'${MCP_BENCHMARK_FLAG}'`);
111
+ }
112
+
113
+ async function setTomlBenchmark(
114
+ path: string,
115
+ enabled: boolean,
116
+ ): Promise<InstallAction | "missing"> {
117
+ if (!(await Bun.file(path).exists())) {
118
+ return "missing";
119
+ }
120
+ const text = await Bun.file(path).text();
121
+ if (!text.includes(CODEX_MCP_HEADER)) {
122
+ return "missing";
123
+ }
124
+ const current = await statusForToml(path);
125
+ if (current === enabled) {
126
+ return "unchanged";
127
+ }
128
+
129
+ // Replace the miru block with the desired args, preserving the rest of the file.
130
+ await removeTomlBlock(path);
131
+ const existed = await Bun.file(path).exists();
132
+ const existing = existed ? await Bun.file(path).text() : "";
133
+ const base = existing.replace(/\n+$/, "");
134
+ const block = codexMcpBlock(enabled);
135
+ const next = base.length > 0 ? `${base}\n\n${block}` : block;
136
+ await Bun.write(path, next.endsWith("\n") ? next : `${next}\n`);
137
+ return "updated";
138
+ }
139
+
140
+ async function statusForJson(agent: AgentTarget): Promise<boolean | null> {
141
+ const mcp = agent.mcp;
142
+ if (mcp?.format !== "json") {
143
+ return null;
144
+ }
145
+ const parsed = await readJsonObject(mcp.path);
146
+ if (!parsed) {
147
+ return null;
148
+ }
149
+ const section = parsed[mcp.key];
150
+ if (!section || typeof section !== "object" || Array.isArray(section)) {
151
+ return null;
152
+ }
153
+ const entry = (section as Record<string, unknown>)[mcp.memberKey];
154
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
155
+ return null;
156
+ }
157
+ return mcpEntryHasBenchmark(entry as Record<string, unknown>);
158
+ }
159
+
160
+ async function setJsonBenchmark(
161
+ agent: AgentTarget,
162
+ enabled: boolean,
163
+ ): Promise<InstallAction | "missing"> {
164
+ const mcp = agent.mcp;
165
+ if (mcp?.format !== "json") {
166
+ return "missing";
167
+ }
168
+ const parsed = await readJsonObject(mcp.path);
169
+ if (!parsed) {
170
+ return "missing";
171
+ }
172
+ const sectionRaw = parsed[mcp.key];
173
+ if (!sectionRaw || typeof sectionRaw !== "object" || Array.isArray(sectionRaw)) {
174
+ return "missing";
175
+ }
176
+ const section = sectionRaw as Record<string, unknown>;
177
+ const entryRaw = section[mcp.memberKey];
178
+ if (!entryRaw || typeof entryRaw !== "object" || Array.isArray(entryRaw)) {
179
+ return "missing";
180
+ }
181
+
182
+ const applied = applyBenchmarkFlagToMcpEntry(entryRaw as Record<string, unknown>, enabled);
183
+ if (!applied.changed) {
184
+ return "unchanged";
185
+ }
186
+ section[mcp.memberKey] = applied.entry;
187
+ parsed[mcp.key] = section;
188
+ await writeJsonObject(mcp.path, parsed);
189
+ return "updated";
190
+ }
191
+
192
+ /** Inspect installed Miru MCP configs for `--benchmark`. */
193
+ export async function getBenchmarkModeStatus(): Promise<BenchmarkModeTargetResult[]> {
194
+ const results: BenchmarkModeTargetResult[] = [];
195
+ for (const agent of AGENT_TARGETS) {
196
+ const mcp = agent.mcp;
197
+ if (!mcp) {
198
+ continue;
199
+ }
200
+ if (mcp.format === "toml") {
201
+ const enabled = await statusForToml(mcp.path);
202
+ results.push({
203
+ agent: agent.displayName,
204
+ path: mcp.path,
205
+ action: enabled == null ? "missing" : enabled ? "enabled" : "disabled",
206
+ enabled: enabled === true,
207
+ });
208
+ continue;
209
+ }
210
+ const enabled = await statusForJson(agent);
211
+ results.push({
212
+ agent: agent.displayName,
213
+ path: mcp.path,
214
+ action: enabled == null ? "missing" : enabled ? "enabled" : "disabled",
215
+ enabled: enabled === true,
216
+ });
217
+ }
218
+ return results;
219
+ }
220
+
221
+ /** Enable or disable `--benchmark` on every installed Miru MCP entry. */
222
+ export async function setBenchmarkMode(enabled: boolean): Promise<BenchmarkModeTargetResult[]> {
223
+ const results: BenchmarkModeTargetResult[] = [];
224
+ for (const agent of AGENT_TARGETS) {
225
+ const mcp = agent.mcp;
226
+ if (!mcp) {
227
+ continue;
228
+ }
229
+ if (mcp.format === "toml") {
230
+ const action = await setTomlBenchmark(mcp.path, enabled);
231
+ results.push({
232
+ agent: agent.displayName,
233
+ path: mcp.path,
234
+ action,
235
+ enabled: action === "missing" ? false : enabled,
236
+ });
237
+ continue;
238
+ }
239
+ const action = await setJsonBenchmark(agent, enabled);
240
+ results.push({
241
+ agent: agent.displayName,
242
+ path: mcp.path,
243
+ action,
244
+ enabled: action === "missing" ? false : enabled,
245
+ });
246
+ }
247
+ return results;
248
+ }