@takara-ai/miru-code 0.1.3 → 0.2.0
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 +234 -36
- package/package.json +1 -2
- package/src/cli.ts +42 -12
- package/src/credentials.ts +101 -0
- package/src/embeddings/openai.ts +1 -1
- package/src/embeddings/validate.ts +94 -0
- package/src/env.ts +4 -2
- package/src/index/dense.ts +8 -0
- package/src/index/incremental.ts +130 -0
- package/src/index/quantize.ts +24 -0
- package/src/index/vectors.ts +18 -0
- package/src/mcp/index-cache.ts +162 -93
- package/src/mcp/serve.ts +3 -13
- package/src/mcp/server.ts +10 -10
- package/src/miru-index.ts +55 -12
- package/src/setup.ts +168 -0
- package/src/spinner.ts +57 -0
package/README.md
CHANGED
|
@@ -2,9 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
Hybrid code search for AI agents — **TypeScript**, **Bun**, and **Takara embeddings**.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## What is Miru?
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
*Miru* (見る) means “to see” or “to look” in Japanese. Miru helps coding agents **find code by meaning** instead of guessing file paths or running exhaustive greps.
|
|
8
|
+
|
|
9
|
+
When an agent asks “where is auth middleware configured?” or “how do we batch embeddings?”, Miru returns the most relevant **chunks** of your codebase — file path, line range, and snippet — so the agent can read only what matters.
|
|
10
|
+
|
|
11
|
+
### Why hybrid search?
|
|
12
|
+
|
|
13
|
+
Pure semantic search misses exact symbol matches. Pure keyword search misses paraphrased intent. Miru combines both:
|
|
14
|
+
|
|
15
|
+
1. **Dense (semantic)** — Takara code embeddings (`ds1-potion-code-16m`) over structurally chunked source files.
|
|
16
|
+
2. **Sparse (BM25)** — keyword scoring with an inverted index; parallelized in Bun workers on large repos.
|
|
17
|
+
3. **RRF fusion** — reciprocal rank fusion merges the two ranked lists.
|
|
18
|
+
4. **Reranking** — code-tuned penalties and boosts (e.g. multi-chunk files, query-term overlap).
|
|
19
|
+
|
|
20
|
+
Indexes are built on demand, cached on disk, and invalidated when files change. You can search **code**, **docs**, **config**, or **all** content types.
|
|
21
|
+
|
|
22
|
+
### How agents use Miru
|
|
23
|
+
|
|
24
|
+
| Mode | Best for | What the agent gets |
|
|
25
|
+
|------|----------|---------------------|
|
|
26
|
+
| **MCP server** | Cursor, Claude Code, VS Code Copilot, Gemini CLI, Kiro, OpenCode | Native `search` and `find_related` tools over the workspace |
|
|
27
|
+
| **CLI sub-agent** | Any agent with shell access | `miru init --agent <id>` writes a specialist agent that runs `miru search` / `miru find-related` via Bash |
|
|
28
|
+
|
|
29
|
+
Both modes share the same index and cache. Pick MCP when your IDE supports it; use the sub-agent when you want a dedicated exploration agent without MCP wiring.
|
|
8
30
|
|
|
9
31
|
## Install
|
|
10
32
|
|
|
@@ -12,7 +34,7 @@ Requires [Bun](https://bun.sh) 1.1+ on your PATH (`curl -fsSL https://bun.sh/ins
|
|
|
12
34
|
|
|
13
35
|
```bash
|
|
14
36
|
# Run once without installing
|
|
15
|
-
|
|
37
|
+
bunx @takara-ai/miru-code search "auth middleware" ./src
|
|
16
38
|
|
|
17
39
|
# Global CLI
|
|
18
40
|
bun add -g @takara-ai/miru-code
|
|
@@ -34,7 +56,7 @@ const index = await MiruIndex.fromPath("./src");
|
|
|
34
56
|
bun install
|
|
35
57
|
```
|
|
36
58
|
|
|
37
|
-
Add credentials to `.env.local` (loaded automatically when you run from this repo), or pass them in your MCP config `env` block (recommended
|
|
59
|
+
Add credentials to `.env.local` (loaded automatically when you run from this repo), or pass them in your MCP config `env` block (recommended — MCP does not inherit IDE env vars reliably).
|
|
38
60
|
|
|
39
61
|
| Variable | Default |
|
|
40
62
|
|----------|---------|
|
|
@@ -52,64 +74,153 @@ Parallelism: file reads/chunking, embedding batches (up to concurrency in flight
|
|
|
52
74
|
|
|
53
75
|
`OPENAI_*` env aliases are also accepted where noted in the CLI help.
|
|
54
76
|
|
|
55
|
-
##
|
|
77
|
+
## IDE integration
|
|
78
|
+
|
|
79
|
+
Miru supports six agent platforms. For each, you can wire the **MCP server** (recommended) and/or install a **CLI sub-agent** with `miru init`.
|
|
80
|
+
|
|
81
|
+
### Quick reference
|
|
82
|
+
|
|
83
|
+
| IDE | MCP config file | Sub-agent command | Sub-agent output |
|
|
84
|
+
|-----|-----------------|-------------------|------------------|
|
|
85
|
+
| [Cursor](#cursor) | `~/.cursor/mcp.json` or `.cursor/mcp.json` | `miru init --agent cursor` | `.cursor/agents/miru-code.md` |
|
|
86
|
+
| [Claude Code](#claude-code) | `.mcp.json` (project root) or `~/.claude.json` | `miru init --agent claude` | `.claude/agents/miru-code.md` |
|
|
87
|
+
| [VS Code / Copilot](#vs-code--github-copilot) | `.vscode/mcp.json` or user profile `mcp.json` | `miru init --agent copilot` | `.github/agents/miru-code.md` |
|
|
88
|
+
| [Gemini CLI](#gemini-cli) | `~/.gemini/settings.json` or `.gemini/settings.json` | `miru init --agent gemini` | `.gemini/agents/miru-code.md` |
|
|
89
|
+
| [Kiro](#kiro) | `~/.kiro/settings/mcp.json` or `.kiro/settings/mcp.json` | `miru init --agent kiro` | `.kiro/agents/miru-code.md` |
|
|
90
|
+
| [OpenCode](#opencode) | `~/.config/opencode/opencode.json` or `opencode.json` | `miru init --agent opencode` | `.opencode/agents/miru-code.md` |
|
|
91
|
+
|
|
92
|
+
Run `miru init --agent <id> --force` to overwrite an existing sub-agent file.
|
|
93
|
+
|
|
94
|
+
### MCP server
|
|
95
|
+
|
|
96
|
+
Run with **no arguments** to start the stdio MCP server. Indexing begins on the first `search` or `find_related` call, using the `repo` path you pass. Optional `--ref` sets the default git ref for remote repos.
|
|
56
97
|
|
|
57
98
|
```bash
|
|
58
|
-
|
|
59
|
-
|
|
99
|
+
bunx @takara-ai/miru-code
|
|
100
|
+
# or after bun add -g @takara-ai/miru-code: miru
|
|
101
|
+
```
|
|
60
102
|
|
|
61
|
-
|
|
62
|
-
bun run miru find-related src/embeddings/openai.ts 120 ./src
|
|
103
|
+
**Tools:** `search`, `find_related` — `repo` is required (project root or https:// git URL).
|
|
63
104
|
|
|
64
|
-
|
|
65
|
-
bun run miru init --agent cursor
|
|
105
|
+
For MCP configs, use `bunx` as the command (`bun` with `x` in `args` works too — they're equivalent).
|
|
66
106
|
|
|
67
|
-
|
|
68
|
-
|
|
107
|
+
Put the bearer token in `env`; MCP will not read `.env.local` unless those vars are unset.
|
|
108
|
+
|
|
109
|
+
#### Shared env block
|
|
110
|
+
|
|
111
|
+
Use this in every MCP config below (adjust the key name if your IDE uses `environment` instead of `env`):
|
|
112
|
+
|
|
113
|
+
```json
|
|
114
|
+
{
|
|
115
|
+
"TAKARA_API_KEY": "your-takara-bearer-token",
|
|
116
|
+
"MIRU_OPENAI_BASE_URL": "https://infer.dev.takara.ai/v1",
|
|
117
|
+
"MIRU_OPENAI_EMBEDDING_MODEL": "ds1-potion-code-16m",
|
|
118
|
+
"MIRU_EMBEDDING_DIMENSIONS": "256"
|
|
119
|
+
}
|
|
69
120
|
```
|
|
70
121
|
|
|
71
|
-
|
|
122
|
+
### Cursor
|
|
72
123
|
|
|
73
|
-
|
|
124
|
+
**MCP** — `~/.cursor/mcp.json` or project `.cursor/mcp.json`:
|
|
74
125
|
|
|
75
|
-
|
|
126
|
+
```json
|
|
127
|
+
{
|
|
128
|
+
"mcpServers": {
|
|
129
|
+
"miru": {
|
|
130
|
+
"command": "bunx",
|
|
131
|
+
"args": ["@takara-ai/miru-code"],
|
|
132
|
+
"env": {
|
|
133
|
+
"TAKARA_API_KEY": "your-takara-bearer-token"
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
After `bun add -g @takara-ai/miru-code`, you can use `"command": "miru"` instead.
|
|
141
|
+
|
|
142
|
+
**Sub-agent:**
|
|
76
143
|
|
|
77
144
|
```bash
|
|
78
|
-
|
|
79
|
-
# or after bun add -g @takara-ai/miru-code: miru
|
|
80
|
-
# or without installing: bun x @takara-ai/miru-code
|
|
145
|
+
miru init --agent cursor
|
|
81
146
|
```
|
|
82
147
|
|
|
83
|
-
|
|
148
|
+
Commit `.cursor/agents/miru-code.md` to share the exploration agent with your team.
|
|
84
149
|
|
|
85
|
-
|
|
150
|
+
### Claude Code
|
|
86
151
|
|
|
87
|
-
|
|
152
|
+
**MCP** — project `.mcp.json` (check into git) or user scope in `~/.claude.json`:
|
|
88
153
|
|
|
89
154
|
```json
|
|
90
155
|
{
|
|
91
156
|
"mcpServers": {
|
|
92
157
|
"miru": {
|
|
93
|
-
"
|
|
94
|
-
"
|
|
158
|
+
"type": "stdio",
|
|
159
|
+
"command": "bunx",
|
|
160
|
+
"args": ["@takara-ai/miru-code"],
|
|
95
161
|
"env": {
|
|
96
|
-
"TAKARA_API_KEY": "
|
|
97
|
-
"MIRU_OPENAI_BASE_URL": "https://infer.dev.takara.ai/v1",
|
|
98
|
-
"MIRU_OPENAI_EMBEDDING_MODEL": "ds1-potion-code-16m",
|
|
99
|
-
"MIRU_EMBEDDING_DIMENSIONS": "256"
|
|
162
|
+
"TAKARA_API_KEY": "${TAKARA_API_KEY}"
|
|
100
163
|
}
|
|
101
164
|
}
|
|
102
165
|
}
|
|
103
166
|
}
|
|
104
167
|
```
|
|
105
168
|
|
|
106
|
-
|
|
169
|
+
Claude Code supports `${VAR}` expansion in config files. Restart the session and run `/mcp` to verify the connection.
|
|
170
|
+
|
|
171
|
+
Or via CLI:
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
claude mcp add miru -s project -- bunx @takara-ai/miru-code
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
**Sub-agent:**
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
miru init --agent claude
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Writes `.claude/agents/miru-code.md` — invoke the `miru-code` agent for semantic exploration tasks.
|
|
184
|
+
|
|
185
|
+
### VS Code / GitHub Copilot
|
|
186
|
+
|
|
187
|
+
**MCP** — workspace `.vscode/mcp.json` or user profile (Command Palette: **MCP: Open User Configuration**):
|
|
188
|
+
|
|
189
|
+
```json
|
|
190
|
+
{
|
|
191
|
+
"servers": {
|
|
192
|
+
"miru": {
|
|
193
|
+
"type": "stdio",
|
|
194
|
+
"command": "bunx",
|
|
195
|
+
"args": ["@takara-ai/miru-code"],
|
|
196
|
+
"env": {
|
|
197
|
+
"TAKARA_API_KEY": "your-takara-bearer-token"
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
VS Code uses `"servers"` (not `mcpServers`). Use **MCP: List Servers** to start or restart the server after editing the config.
|
|
205
|
+
|
|
206
|
+
**Sub-agent:**
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
miru init --agent copilot
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Writes `.github/agents/miru-code.md` for Copilot’s agent system.
|
|
213
|
+
|
|
214
|
+
### Gemini CLI
|
|
215
|
+
|
|
216
|
+
**MCP** — `~/.gemini/settings.json` or project `.gemini/settings.json`:
|
|
107
217
|
|
|
108
218
|
```json
|
|
109
219
|
{
|
|
110
220
|
"mcpServers": {
|
|
111
221
|
"miru": {
|
|
112
|
-
"command": "
|
|
222
|
+
"command": "bunx",
|
|
223
|
+
"args": ["@takara-ai/miru-code"],
|
|
113
224
|
"env": {
|
|
114
225
|
"TAKARA_API_KEY": "your-takara-bearer-token"
|
|
115
226
|
}
|
|
@@ -118,15 +229,58 @@ Alternatively, after `bun add -g @takara-ai/miru-code`:
|
|
|
118
229
|
}
|
|
119
230
|
```
|
|
120
231
|
|
|
121
|
-
|
|
232
|
+
Or add interactively: `gemini mcp add`. Run `/mcp` inside a session to confirm tools are available.
|
|
233
|
+
|
|
234
|
+
**Sub-agent:**
|
|
235
|
+
|
|
236
|
+
```bash
|
|
237
|
+
miru init --agent gemini
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Writes `.gemini/agents/miru-code.md`.
|
|
241
|
+
|
|
242
|
+
### Kiro
|
|
243
|
+
|
|
244
|
+
**MCP** — `~/.kiro/settings/mcp.json` or workspace `.kiro/settings/mcp.json`:
|
|
122
245
|
|
|
123
246
|
```json
|
|
124
247
|
{
|
|
125
248
|
"mcpServers": {
|
|
126
249
|
"miru": {
|
|
127
|
-
"command": "
|
|
128
|
-
"args": ["/
|
|
250
|
+
"command": "bunx",
|
|
251
|
+
"args": ["@takara-ai/miru-code"],
|
|
129
252
|
"env": {
|
|
253
|
+
"TAKARA_API_KEY": "your-takara-bearer-token",
|
|
254
|
+
"PATH": "/usr/local/bin:/usr/bin:/bin"
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
Kiro runs with a minimal environment — include `PATH` if Bun is not on the default path. Approve env vars when prompted in the IDE.
|
|
262
|
+
|
|
263
|
+
**Sub-agent:**
|
|
264
|
+
|
|
265
|
+
```bash
|
|
266
|
+
miru init --agent kiro
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
Writes `.kiro/agents/miru-code.md`.
|
|
270
|
+
|
|
271
|
+
### OpenCode
|
|
272
|
+
|
|
273
|
+
**MCP** — `~/.config/opencode/opencode.json` or project `opencode.json`:
|
|
274
|
+
|
|
275
|
+
```json
|
|
276
|
+
{
|
|
277
|
+
"$schema": "https://opencode.ai/config.json",
|
|
278
|
+
"mcp": {
|
|
279
|
+
"miru": {
|
|
280
|
+
"type": "local",
|
|
281
|
+
"command": ["bunx", "@takara-ai/miru-code"],
|
|
282
|
+
"enabled": true,
|
|
283
|
+
"environment": {
|
|
130
284
|
"TAKARA_API_KEY": "your-takara-bearer-token"
|
|
131
285
|
}
|
|
132
286
|
}
|
|
@@ -134,7 +288,49 @@ Local development (path to this repo):
|
|
|
134
288
|
}
|
|
135
289
|
```
|
|
136
290
|
|
|
137
|
-
|
|
291
|
+
OpenCode uses the `mcp` key (not `mcpServers`), requires `"type": "local"`, and uses `environment` instead of `env`. Verify with `opencode mcp list`.
|
|
292
|
+
|
|
293
|
+
**Sub-agent:**
|
|
294
|
+
|
|
295
|
+
```bash
|
|
296
|
+
miru init --agent opencode
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
Writes `.opencode/agents/miru-code.md` (subagent mode with bash/read permissions).
|
|
300
|
+
|
|
301
|
+
### Local development (any IDE)
|
|
302
|
+
|
|
303
|
+
Point MCP at this repo instead of the published package:
|
|
304
|
+
|
|
305
|
+
```json
|
|
306
|
+
{
|
|
307
|
+
"command": "bun",
|
|
308
|
+
"args": ["/absolute/path/to/miru-code/src/cli.ts"],
|
|
309
|
+
"env": {
|
|
310
|
+
"TAKARA_API_KEY": "your-takara-bearer-token"
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
Merge into the appropriate wrapper key (`mcpServers`, `servers`, or `mcp`) for your IDE.
|
|
316
|
+
|
|
317
|
+
## CLI
|
|
318
|
+
|
|
319
|
+
```bash
|
|
320
|
+
# Hybrid search (positional, Python-compatible)
|
|
321
|
+
bun run miru search "where are embeddings created" ./src -k 5
|
|
322
|
+
|
|
323
|
+
# Find semantically related chunks
|
|
324
|
+
bun run miru find-related src/embeddings/openai.ts 120 ./src
|
|
325
|
+
|
|
326
|
+
# Write a miru-code sub-agent for your IDE
|
|
327
|
+
bun run miru init --agent cursor
|
|
328
|
+
|
|
329
|
+
# Clear disk cache
|
|
330
|
+
bun run miru clear ./src
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
Indexes are cached under `~/Library/Caches/miru` (macOS) or `~/.cache/miru` (Linux).
|
|
138
334
|
|
|
139
335
|
## Library
|
|
140
336
|
|
|
@@ -156,12 +352,14 @@ bun run typecheck
|
|
|
156
352
|
|
|
157
353
|
Releases use **Bun** for install, test, and `bun pm pack`. The registry upload uses **`npm publish`** on that tarball because [Bun does not yet support npm OIDC trusted publishing](https://github.com/oven-sh/bun/issues/22423) — no long-lived npm tokens in GitHub secrets.
|
|
158
354
|
|
|
159
|
-
**Local
|
|
355
|
+
**Local publish** (logged in to npm; `publishConfig.access` is already `public`):
|
|
160
356
|
|
|
161
357
|
```bash
|
|
162
|
-
bun publish
|
|
358
|
+
bun publish
|
|
163
359
|
```
|
|
164
360
|
|
|
361
|
+
Runs `prepublishOnly` (typecheck + tests), then a single registry upload. Do not add a `publish` script that calls `npm publish` — Bun runs lifecycle scripts after its own upload and would publish twice.
|
|
362
|
+
|
|
165
363
|
**CI** (after Trusted Publisher is configured on npm):
|
|
166
364
|
|
|
167
365
|
```bash
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@takara-ai/miru-code",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Miru (見る) — hybrid code search for agents. Bun, TypeScript, Takara embeddings.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -50,7 +50,6 @@
|
|
|
50
50
|
"lint": "biome check .",
|
|
51
51
|
"lint:fix": "biome check --write .",
|
|
52
52
|
"prepublishOnly": "bun run typecheck && bun test",
|
|
53
|
-
"publish": "npm publish --access public",
|
|
54
53
|
"benchmark:quality": "bun run scripts/benchmark-quality.ts",
|
|
55
54
|
"benchmark:quantization": "bun run scripts/benchmark-quantization.ts"
|
|
56
55
|
},
|
package/src/cli.ts
CHANGED
|
@@ -2,15 +2,18 @@
|
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import { type AgentId, writeAgentFile } from "./agents.ts";
|
|
4
4
|
import { clearCache } from "./cache.ts";
|
|
5
|
+
import { loadStoredCredentials } from "./credentials.ts";
|
|
5
6
|
import { loadEnvFiles } from "./env-files.ts";
|
|
6
7
|
import { serveMcp } from "./mcp/serve.ts";
|
|
7
8
|
import { MiruIndex } from "./miru-index.ts";
|
|
9
|
+
import { ensureCredentials, runClearCredentials, runSetup } from "./setup.ts";
|
|
8
10
|
import type { ContentType } from "./types.ts";
|
|
9
|
-
import { formatResults,
|
|
11
|
+
import { formatResults, resolveChunk, resolveContent } from "./utils.ts";
|
|
10
12
|
|
|
11
13
|
loadEnvFiles();
|
|
14
|
+
await loadStoredCredentials();
|
|
12
15
|
|
|
13
|
-
const CLI_COMMANDS = new Set(["search", "find-related", "init", "clear", "-h", "--help"]);
|
|
16
|
+
const CLI_COMMANDS = new Set(["search", "find-related", "init", "setup", "clear", "-h", "--help"]);
|
|
14
17
|
|
|
15
18
|
const AGENTS = new Set<AgentId>(["claude", "copilot", "cursor", "gemini", "kiro", "opencode"]);
|
|
16
19
|
|
|
@@ -64,6 +67,7 @@ async function runSearch(
|
|
|
64
67
|
topK: number,
|
|
65
68
|
content: ContentType[],
|
|
66
69
|
): Promise<void> {
|
|
70
|
+
await ensureCredentials();
|
|
67
71
|
const index = await MiruIndex.fromSource(path, content);
|
|
68
72
|
await index.saveToDefaultCache(path);
|
|
69
73
|
const results = await index.search({ query, topK });
|
|
@@ -79,6 +83,7 @@ async function runFindRelated(
|
|
|
79
83
|
topK: number,
|
|
80
84
|
content: ContentType[],
|
|
81
85
|
): Promise<void> {
|
|
86
|
+
await ensureCredentials();
|
|
82
87
|
const index = await MiruIndex.fromSource(path, content);
|
|
83
88
|
const chunk = resolveChunk(index.chunks, filePath, line);
|
|
84
89
|
if (!chunk) {
|
|
@@ -111,14 +116,14 @@ function printHelp(): void {
|
|
|
111
116
|
CLI:
|
|
112
117
|
miru search <query> [path] [-k N] [--content code|docs|config|all]
|
|
113
118
|
miru find-related <file_path> <line> [path] [-k N] [--content ...]
|
|
119
|
+
miru setup [--key KEY] [--force] [--clear]
|
|
114
120
|
miru init [--agent cursor] [--force]
|
|
115
121
|
miru clear [path]
|
|
116
122
|
|
|
117
123
|
MCP server (default when no CLI subcommand):
|
|
118
|
-
miru
|
|
119
|
-
miru [path] [--ref BRANCH] [--content code ...]
|
|
124
|
+
miru [--ref BRANCH] [--content code ...]
|
|
120
125
|
|
|
121
|
-
|
|
126
|
+
Indexes a repo on the first search/find_related call via the tool repo argument.
|
|
122
127
|
|
|
123
128
|
Examples:
|
|
124
129
|
miru search "auth middleware" ./src
|
|
@@ -156,6 +161,32 @@ async function runCli(argv: string[]): Promise<void> {
|
|
|
156
161
|
return;
|
|
157
162
|
}
|
|
158
163
|
|
|
164
|
+
if (command === "setup") {
|
|
165
|
+
let apiKey: string | undefined;
|
|
166
|
+
let force = false;
|
|
167
|
+
let clear = false;
|
|
168
|
+
for (let i = 0; i < rest.length; i++) {
|
|
169
|
+
const arg = rest[i];
|
|
170
|
+
if (arg === "--force") {
|
|
171
|
+
force = true;
|
|
172
|
+
} else if (arg === "--clear") {
|
|
173
|
+
clear = true;
|
|
174
|
+
} else if ((arg === "--key" || arg === "-k") && rest[i + 1]) {
|
|
175
|
+
apiKey = rest[++i];
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (clear) {
|
|
179
|
+
if (apiKey) {
|
|
180
|
+
console.error("Usage: miru setup --clear cannot be combined with --key.");
|
|
181
|
+
process.exit(1);
|
|
182
|
+
}
|
|
183
|
+
await runClearCredentials();
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
await runSetup({ apiKey, force });
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
159
190
|
if (command === "clear") {
|
|
160
191
|
const path = resolve(rest[0] ?? process.cwd());
|
|
161
192
|
await runClear(path);
|
|
@@ -194,7 +225,6 @@ async function runCli(argv: string[]): Promise<void> {
|
|
|
194
225
|
}
|
|
195
226
|
|
|
196
227
|
async function runMcp(argv: string[]): Promise<void> {
|
|
197
|
-
let path: string | null = null;
|
|
198
228
|
let ref: string | null = null;
|
|
199
229
|
const contentTokens: string[] = [];
|
|
200
230
|
|
|
@@ -215,20 +245,20 @@ async function runMcp(argv: string[]): Promise<void> {
|
|
|
215
245
|
contentTokens.push(value);
|
|
216
246
|
i++;
|
|
217
247
|
}
|
|
218
|
-
continue;
|
|
219
|
-
}
|
|
220
|
-
if (arg && !arg.startsWith("-") && path === null) {
|
|
221
|
-
path = isGitUrl(arg) ? arg : resolve(arg);
|
|
222
248
|
}
|
|
223
249
|
}
|
|
224
250
|
|
|
225
251
|
await serveMcp({
|
|
226
|
-
path,
|
|
227
252
|
ref,
|
|
228
253
|
content: resolveContent(contentTokens.length > 0 ? contentTokens : ["code"]),
|
|
229
254
|
});
|
|
230
255
|
}
|
|
231
256
|
|
|
257
|
+
async function runMcpWithCredentials(argv: string[]): Promise<void> {
|
|
258
|
+
await ensureCredentials({ interactive: false });
|
|
259
|
+
await runMcp(argv);
|
|
260
|
+
}
|
|
261
|
+
|
|
232
262
|
async function main(): Promise<void> {
|
|
233
263
|
const argv = process.argv.slice(2);
|
|
234
264
|
const first = argv[0];
|
|
@@ -236,7 +266,7 @@ async function main(): Promise<void> {
|
|
|
236
266
|
await runCli(argv);
|
|
237
267
|
return;
|
|
238
268
|
}
|
|
239
|
-
await
|
|
269
|
+
await runMcpWithCredentials(argv);
|
|
240
270
|
}
|
|
241
271
|
|
|
242
272
|
main().catch((err) => {
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { chmod, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { EMBEDDING_API_KEY_NAMES, envFirstString } from "./env.ts";
|
|
4
|
+
|
|
5
|
+
const CREDENTIALS_FILENAME = "credentials.json";
|
|
6
|
+
const CREDENTIALS_VERSION = 1;
|
|
7
|
+
|
|
8
|
+
interface StoredCredentials {
|
|
9
|
+
version: number;
|
|
10
|
+
takara_api_key: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function resolveCredentialsDir(): string {
|
|
14
|
+
const override = process.env.MIRU_CREDENTIALS_DIR;
|
|
15
|
+
if (override) {
|
|
16
|
+
return override;
|
|
17
|
+
}
|
|
18
|
+
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
19
|
+
if (process.platform === "win32") {
|
|
20
|
+
const base = process.env.APPDATA ?? join(home, "AppData", "Roaming");
|
|
21
|
+
return join(base, "miru");
|
|
22
|
+
}
|
|
23
|
+
if (process.platform === "darwin") {
|
|
24
|
+
return join(home, "Library", "Application Support", "miru");
|
|
25
|
+
}
|
|
26
|
+
const xdg = process.env.XDG_CONFIG_HOME ?? join(home, ".config");
|
|
27
|
+
return join(xdg, "miru");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function resolveCredentialsPath(): string {
|
|
31
|
+
return join(resolveCredentialsDir(), CREDENTIALS_FILENAME);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function hasEmbeddingApiKeyInEnv(): boolean {
|
|
35
|
+
return Boolean(envFirstString([...EMBEDDING_API_KEY_NAMES], ""));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function readStoredCredentials(): Promise<StoredCredentials | null> {
|
|
39
|
+
const path = resolveCredentialsPath();
|
|
40
|
+
if (!(await Bun.file(path).exists())) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
const parsed = JSON.parse(await readFile(path, "utf-8")) as StoredCredentials;
|
|
45
|
+
if (parsed.version !== CREDENTIALS_VERSION || !parsed.takara_api_key) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
return parsed;
|
|
49
|
+
} catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Set TAKARA_API_KEY from the user credentials file when env is unset. */
|
|
55
|
+
export async function loadStoredCredentials(): Promise<boolean> {
|
|
56
|
+
if (hasEmbeddingApiKeyInEnv()) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
const stored = await readStoredCredentials();
|
|
60
|
+
if (!stored) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
process.env.TAKARA_API_KEY = stored.takara_api_key;
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export async function saveStoredCredentials(apiKey: string): Promise<string> {
|
|
68
|
+
const dir = resolveCredentialsDir();
|
|
69
|
+
const path = resolveCredentialsPath();
|
|
70
|
+
await mkdir(dir, { recursive: true, mode: 0o700 });
|
|
71
|
+
const payload: StoredCredentials = {
|
|
72
|
+
version: CREDENTIALS_VERSION,
|
|
73
|
+
takara_api_key: apiKey,
|
|
74
|
+
};
|
|
75
|
+
await writeFile(path, `${JSON.stringify(payload, null, 2)}\n`, {
|
|
76
|
+
encoding: "utf-8",
|
|
77
|
+
mode: 0o600,
|
|
78
|
+
});
|
|
79
|
+
try {
|
|
80
|
+
await chmod(path, 0o600);
|
|
81
|
+
} catch {
|
|
82
|
+
// Windows may not support Unix mode bits on all filesystems.
|
|
83
|
+
}
|
|
84
|
+
return path;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export async function clearStoredCredentials(): Promise<{ cleared: boolean; path: string }> {
|
|
88
|
+
const path = resolveCredentialsPath();
|
|
89
|
+
if (!(await Bun.file(path).exists())) {
|
|
90
|
+
return { cleared: false, path };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const stored = await readStoredCredentials();
|
|
94
|
+
await rm(path, { force: true });
|
|
95
|
+
|
|
96
|
+
if (stored && process.env.TAKARA_API_KEY === stored.takara_api_key) {
|
|
97
|
+
delete process.env.TAKARA_API_KEY;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return { cleared: true, path };
|
|
101
|
+
}
|
package/src/embeddings/openai.ts
CHANGED
|
@@ -72,7 +72,7 @@ function stripLoneSurrogates(text: string): string {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
export function sanitizeEmbeddingInput(text: string): string {
|
|
75
|
-
|
|
75
|
+
const out = stripLoneSurrogates(text);
|
|
76
76
|
// Some OpenAI-compatible gateways mis-handle backslash escapes in JSON text
|
|
77
77
|
// payloads. Keep a benchmark fallback mode to forcefully neutralize them.
|
|
78
78
|
const mode = process.env.MIRU_EMBED_ESCAPE_MODE ?? process.env.SEMBLE_EMBED_ESCAPE_MODE ?? "quad";
|