brain-cache 0.1.0 → 0.3.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 +15 -29
- package/dist/{askCodebase-ECDSSTQ6.js → askCodebase-DTII3Y6P.js} +8 -8
- package/dist/buildContext-JKYV7CCP.js +14 -0
- package/dist/{chunk-PDQXJSH4.js → chunk-3SFDFUEX.js} +5 -1
- package/dist/{chunk-OKWMQNH6.js → chunk-5FXXZBZV.js} +1 -1
- package/dist/{chunk-XXWJ57QP.js → chunk-6MACVOTO.js} +2 -2
- package/dist/{chunk-7JLSJNKU.js → chunk-ABKGOJTC.js} +7 -7
- package/dist/{chunk-ZLB4VJQK.js → chunk-BF5UDEIF.js} +1 -1
- package/dist/{chunk-WCNMLSL2.js → chunk-GR6QXZ4J.js} +6 -8
- package/dist/{chunk-PA4BZBWS.js → chunk-MSI4MDIM.js} +1 -1
- package/dist/{chunk-P7WSTGLE.js → chunk-V4ARVFRG.js} +1 -1
- package/dist/cli.js +8 -8
- package/dist/{doctor-5775VUMA.js → doctor-3RIVSSNB.js} +3 -3
- package/dist/{embedder-KRANITVN.js → embedder-2UG2GDQO.js} +2 -2
- package/dist/{init-TRPFEOHF.js → init-SXC4MWOR.js} +26 -4
- package/dist/mcp.js +195 -177
- package/dist/{search-WKKGPNLV.js → search-BF7QY64J.js} +6 -6
- package/dist/{status-2SOIQ3LX.js → status-JYNMLSXZ.js} +3 -3
- package/dist/{workflows-MJLEPCZY.js → workflows-TWA2GDHJ.js} +194 -176
- package/package.json +1 -1
- package/dist/buildContext-6755TRND.js +0 -14
package/README.md
CHANGED
|
@@ -14,19 +14,7 @@ brain-cache is an MCP server that gives Claude local, indexed access to your cod
|
|
|
14
14
|
|
|
15
15
|
## Use inside Claude Code (MCP)
|
|
16
16
|
|
|
17
|
-
The primary way to use brain-cache is as an MCP server.
|
|
18
|
-
|
|
19
|
-
```json
|
|
20
|
-
// .claude/settings.json
|
|
21
|
-
{
|
|
22
|
-
"mcpServers": {
|
|
23
|
-
"brain-cache": {
|
|
24
|
-
"command": "brain-cache",
|
|
25
|
-
"args": ["mcp"]
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
```
|
|
17
|
+
The primary way to use brain-cache is as an MCP server. Run `brain-cache init` once — it auto-configures `.mcp.json` in your project root so Claude Code connects immediately. No manual JSON setup needed.
|
|
30
18
|
|
|
31
19
|
Claude then has access to:
|
|
32
20
|
|
|
@@ -99,26 +87,24 @@ brain-cache init
|
|
|
99
87
|
brain-cache index
|
|
100
88
|
```
|
|
101
89
|
|
|
102
|
-
`brain-cache init`
|
|
103
|
-
|
|
104
|
-
**Step 3: Add MCP server to Claude Code**
|
|
90
|
+
`brain-cache init` sets up your project: configures `.mcp.json` so Claude Code connects to brain-cache automatically, and appends MCP tool instructions to `CLAUDE.md`. Runs once; idempotent.
|
|
105
91
|
|
|
106
|
-
|
|
107
|
-
// .claude/settings.json
|
|
108
|
-
{
|
|
109
|
-
"mcpServers": {
|
|
110
|
-
"brain-cache": {
|
|
111
|
-
"command": "brain-cache",
|
|
112
|
-
"args": ["mcp"]
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
**Step 4: Use Claude normally**
|
|
92
|
+
**Step 3: Use Claude normally**
|
|
119
93
|
|
|
120
94
|
brain-cache tools are called automatically. You don’t change how you work — the context just gets better.
|
|
121
95
|
|
|
96
|
+
> **Advanced:** `init` creates `.mcp.json` automatically. If you need to customise it manually, the expected shape is:
|
|
97
|
+
> ```json
|
|
98
|
+
> {
|
|
99
|
+
> "mcpServers": {
|
|
100
|
+
> "brain-cache": {
|
|
101
|
+
> "command": "brain-cache",
|
|
102
|
+
> "args": ["mcp"]
|
|
103
|
+
> }
|
|
104
|
+
> }
|
|
105
|
+
> }
|
|
106
|
+
> ```
|
|
107
|
+
|
|
122
108
|
---
|
|
123
109
|
|
|
124
110
|
## 🧩 Core capabilities
|
|
@@ -4,16 +4,16 @@ import {
|
|
|
4
4
|
} from "./chunk-GGOUKACO.js";
|
|
5
5
|
import {
|
|
6
6
|
runBuildContext
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import "./chunk-
|
|
9
|
-
import "./chunk-
|
|
10
|
-
import "./chunk-
|
|
11
|
-
import "./chunk-
|
|
12
|
-
import "./chunk-
|
|
13
|
-
import "./chunk-
|
|
7
|
+
} from "./chunk-ABKGOJTC.js";
|
|
8
|
+
import "./chunk-5FXXZBZV.js";
|
|
9
|
+
import "./chunk-BF5UDEIF.js";
|
|
10
|
+
import "./chunk-GR6QXZ4J.js";
|
|
11
|
+
import "./chunk-V4ARVFRG.js";
|
|
12
|
+
import "./chunk-6MACVOTO.js";
|
|
13
|
+
import "./chunk-MSI4MDIM.js";
|
|
14
14
|
import {
|
|
15
15
|
childLogger
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-3SFDFUEX.js";
|
|
17
17
|
|
|
18
18
|
// src/workflows/askCodebase.ts
|
|
19
19
|
import Anthropic from "@anthropic-ai/sdk";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
runBuildContext
|
|
4
|
+
} from "./chunk-ABKGOJTC.js";
|
|
5
|
+
import "./chunk-5FXXZBZV.js";
|
|
6
|
+
import "./chunk-BF5UDEIF.js";
|
|
7
|
+
import "./chunk-GR6QXZ4J.js";
|
|
8
|
+
import "./chunk-V4ARVFRG.js";
|
|
9
|
+
import "./chunk-6MACVOTO.js";
|
|
10
|
+
import "./chunk-MSI4MDIM.js";
|
|
11
|
+
import "./chunk-3SFDFUEX.js";
|
|
12
|
+
export {
|
|
13
|
+
runBuildContext
|
|
14
|
+
};
|
|
@@ -64,6 +64,9 @@ var logger = pino(
|
|
|
64
64
|
function childLogger(component) {
|
|
65
65
|
return logger.child({ component });
|
|
66
66
|
}
|
|
67
|
+
function setLogLevel(level) {
|
|
68
|
+
logger.level = level;
|
|
69
|
+
}
|
|
67
70
|
|
|
68
71
|
export {
|
|
69
72
|
GLOBAL_CONFIG_DIR,
|
|
@@ -83,5 +86,6 @@ export {
|
|
|
83
86
|
DIAGNOSTIC_SEARCH_LIMIT,
|
|
84
87
|
DEFAULT_TOKEN_BUDGET,
|
|
85
88
|
FILE_HASHES_FILENAME,
|
|
86
|
-
childLogger
|
|
89
|
+
childLogger,
|
|
90
|
+
setLogLevel
|
|
87
91
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
IndexStateSchema
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-MSI4MDIM.js";
|
|
5
5
|
import {
|
|
6
6
|
DEFAULT_EMBEDDING_DIMENSION,
|
|
7
7
|
EMBEDDING_DIMENSIONS,
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
PROJECT_DATA_DIR,
|
|
10
10
|
VECTOR_INDEX_THRESHOLD,
|
|
11
11
|
childLogger
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-3SFDFUEX.js";
|
|
13
13
|
|
|
14
14
|
// src/services/lancedb.ts
|
|
15
15
|
import * as lancedb from "@lancedb/lancedb";
|
|
@@ -2,29 +2,29 @@
|
|
|
2
2
|
import {
|
|
3
3
|
assembleContext,
|
|
4
4
|
countChunkTokens
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-5FXXZBZV.js";
|
|
6
6
|
import {
|
|
7
7
|
RETRIEVAL_STRATEGIES,
|
|
8
8
|
classifyQueryIntent,
|
|
9
9
|
deduplicateChunks,
|
|
10
10
|
searchChunks
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-BF5UDEIF.js";
|
|
12
12
|
import {
|
|
13
13
|
embedBatchWithRetry
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-GR6QXZ4J.js";
|
|
15
15
|
import {
|
|
16
16
|
isOllamaRunning
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-V4ARVFRG.js";
|
|
18
18
|
import {
|
|
19
19
|
openDatabase,
|
|
20
20
|
readIndexState
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-6MACVOTO.js";
|
|
22
22
|
import {
|
|
23
23
|
readProfile
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-MSI4MDIM.js";
|
|
25
25
|
import {
|
|
26
26
|
DEFAULT_TOKEN_BUDGET
|
|
27
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-3SFDFUEX.js";
|
|
28
28
|
|
|
29
29
|
// src/workflows/buildContext.ts
|
|
30
30
|
import { readFile } from "fs/promises";
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
DEFAULT_EMBEDDING_DIMENSION,
|
|
5
5
|
EMBED_TIMEOUT_MS,
|
|
6
6
|
childLogger
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-3SFDFUEX.js";
|
|
8
8
|
|
|
9
9
|
// src/services/embedder.ts
|
|
10
10
|
import ollama from "ollama";
|
|
@@ -40,7 +40,8 @@ function isContextLengthError(err) {
|
|
|
40
40
|
}
|
|
41
41
|
async function embedBatchWithRetry(model, texts, dimension = DEFAULT_EMBEDDING_DIMENSION, attempt = 0) {
|
|
42
42
|
try {
|
|
43
|
-
|
|
43
|
+
const embeddings = await embedBatch(model, texts);
|
|
44
|
+
return { embeddings, skipped: 0 };
|
|
44
45
|
} catch (err) {
|
|
45
46
|
if (attempt === 0 && isConnectionError(err)) {
|
|
46
47
|
log.warn({ model }, "Ollama cold-start suspected, retrying in 5s");
|
|
@@ -50,24 +51,21 @@ async function embedBatchWithRetry(model, texts, dimension = DEFAULT_EMBEDDING_D
|
|
|
50
51
|
if (isContextLengthError(err)) {
|
|
51
52
|
log.warn({ model, batchSize: texts.length }, "Batch exceeded context length, falling back to individual embedding");
|
|
52
53
|
const results = [];
|
|
54
|
+
let skipped = 0;
|
|
53
55
|
for (const text of texts) {
|
|
54
56
|
try {
|
|
55
57
|
const [vec] = await embedBatch(model, [text]);
|
|
56
58
|
results.push(vec);
|
|
57
59
|
} catch (innerErr) {
|
|
58
60
|
if (isContextLengthError(innerErr)) {
|
|
59
|
-
|
|
60
|
-
`
|
|
61
|
-
brain-cache: chunk too large for embedding model, skipping (${text.length} chars)
|
|
62
|
-
`
|
|
63
|
-
);
|
|
61
|
+
skipped++;
|
|
64
62
|
results.push(new Array(dimension).fill(0));
|
|
65
63
|
} else {
|
|
66
64
|
throw innerErr;
|
|
67
65
|
}
|
|
68
66
|
}
|
|
69
67
|
}
|
|
70
|
-
return results;
|
|
68
|
+
return { embeddings: results, skipped };
|
|
71
69
|
}
|
|
72
70
|
throw err;
|
|
73
71
|
}
|
package/dist/cli.js
CHANGED
|
@@ -5,23 +5,23 @@ import {
|
|
|
5
5
|
|
|
6
6
|
// src/cli/index.ts
|
|
7
7
|
import { Command } from "commander";
|
|
8
|
-
var version = "0.
|
|
8
|
+
var version = "0.3.0";
|
|
9
9
|
var program = new Command();
|
|
10
10
|
program.name("brain-cache").description("Local AI runtime \u2014 GPU cache layer for Claude").version(version);
|
|
11
11
|
program.command("init").description("Detect hardware, pull embedding model, create config directory").action(async () => {
|
|
12
|
-
const { runInit } = await import("./init-
|
|
12
|
+
const { runInit } = await import("./init-SXC4MWOR.js");
|
|
13
13
|
await runInit();
|
|
14
14
|
});
|
|
15
15
|
program.command("doctor").description("Report system health: GPU, VRAM tier, Ollama status").action(async () => {
|
|
16
|
-
const { runDoctor } = await import("./doctor-
|
|
16
|
+
const { runDoctor } = await import("./doctor-3RIVSSNB.js");
|
|
17
17
|
await runDoctor();
|
|
18
18
|
});
|
|
19
19
|
program.command("index").description("Index a codebase: parse, chunk, embed, and store in LanceDB").argument("[path]", "Directory to index (defaults to current directory)").option("-f, --force", "Force full reindex, ignoring cached file hashes").action(async (path, opts) => {
|
|
20
|
-
const { runIndex } = await import("./workflows-
|
|
20
|
+
const { runIndex } = await import("./workflows-TWA2GDHJ.js");
|
|
21
21
|
await runIndex(path, { force: opts.force });
|
|
22
22
|
});
|
|
23
23
|
program.command("search").description("Search indexed codebase with a natural language query").argument("<query>", "Natural language query string").option("-n, --limit <n>", "Maximum number of results", "10").option("-p, --path <path>", "Project root directory").action(async (query, opts) => {
|
|
24
|
-
const { runSearch } = await import("./search-
|
|
24
|
+
const { runSearch } = await import("./search-BF7QY64J.js");
|
|
25
25
|
await runSearch(query, {
|
|
26
26
|
limit: parseInt(opts.limit, 10),
|
|
27
27
|
path: opts.path
|
|
@@ -30,12 +30,12 @@ program.command("search").description("Search indexed codebase with a natural la
|
|
|
30
30
|
program.command("status").description(
|
|
31
31
|
"Show index stats: files indexed, chunks stored, last indexed time"
|
|
32
32
|
).argument("[path]", "Project root directory (defaults to current directory)").action(async (path) => {
|
|
33
|
-
const { runStatus } = await import("./status-
|
|
33
|
+
const { runStatus } = await import("./status-JYNMLSXZ.js");
|
|
34
34
|
await runStatus(path);
|
|
35
35
|
});
|
|
36
36
|
program.command("context").description("Build token-budgeted context from codebase for a query").argument("<query>", "Natural language query string").option("-n, --limit <n>", "Maximum number of search results", "10").option("-b, --budget <tokens>", "Token budget for assembled context", "4096").option("-p, --path <path>", "Project root directory").option("--raw", "Output raw JSON (MCP transport compatible)").action(
|
|
37
37
|
async (query, opts) => {
|
|
38
|
-
const { runBuildContext } = await import("./buildContext-
|
|
38
|
+
const { runBuildContext } = await import("./buildContext-JKYV7CCP.js");
|
|
39
39
|
const result = await runBuildContext(query, {
|
|
40
40
|
limit: parseInt(opts.limit, 10),
|
|
41
41
|
maxTokens: parseInt(opts.budget, 10),
|
|
@@ -61,7 +61,7 @@ ${formatTokenSavings({ tokensSent: result.metadata.tokensSent, estimatedWithout,
|
|
|
61
61
|
program.command("ask").description(
|
|
62
62
|
"Ask a natural language question about the codebase \u2014 retrieves context locally, reasons via Claude"
|
|
63
63
|
).argument("<question>", "Natural language question about the codebase").option("-b, --budget <tokens>", "Token budget for context retrieval", "4096").option("-p, --path <path>", "Project root directory").action(async (question, opts) => {
|
|
64
|
-
const { runAskCodebase } = await import("./askCodebase-
|
|
64
|
+
const { runAskCodebase } = await import("./askCodebase-DTII3Y6P.js");
|
|
65
65
|
const result = await runAskCodebase(question, {
|
|
66
66
|
path: opts.path,
|
|
67
67
|
maxContextTokens: parseInt(opts.budget, 10)
|
|
@@ -4,14 +4,14 @@ import {
|
|
|
4
4
|
isOllamaInstalled,
|
|
5
5
|
isOllamaRunning,
|
|
6
6
|
modelMatches
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-V4ARVFRG.js";
|
|
8
8
|
import {
|
|
9
9
|
detectCapabilities,
|
|
10
10
|
readProfile
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-MSI4MDIM.js";
|
|
12
12
|
import {
|
|
13
13
|
PROFILE_PATH
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-3SFDFUEX.js";
|
|
15
15
|
|
|
16
16
|
// src/workflows/doctor.ts
|
|
17
17
|
import ollama from "ollama";
|
|
@@ -5,12 +5,12 @@ import {
|
|
|
5
5
|
isOllamaRunning,
|
|
6
6
|
pullModelIfMissing,
|
|
7
7
|
startOllama
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-V4ARVFRG.js";
|
|
9
9
|
import {
|
|
10
10
|
detectCapabilities,
|
|
11
11
|
writeProfile
|
|
12
|
-
} from "./chunk-
|
|
13
|
-
import "./chunk-
|
|
12
|
+
} from "./chunk-MSI4MDIM.js";
|
|
13
|
+
import "./chunk-3SFDFUEX.js";
|
|
14
14
|
|
|
15
15
|
// src/workflows/init.ts
|
|
16
16
|
import { existsSync, readFileSync, writeFileSync, appendFileSync } from "fs";
|
|
@@ -52,7 +52,7 @@ async function runInit() {
|
|
|
52
52
|
`brain-cache: warming model ${profileWithVersion.embeddingModel} into VRAM...
|
|
53
53
|
`
|
|
54
54
|
);
|
|
55
|
-
const { embedBatchWithRetry } = await import("./embedder-
|
|
55
|
+
const { embedBatchWithRetry } = await import("./embedder-2UG2GDQO.js");
|
|
56
56
|
await embedBatchWithRetry(profileWithVersion.embeddingModel, ["warmup"]);
|
|
57
57
|
process.stderr.write("brain-cache: model warm.\n");
|
|
58
58
|
await writeProfile(profileWithVersion);
|
|
@@ -63,6 +63,28 @@ async function runInit() {
|
|
|
63
63
|
VRAM tier: ${profileWithVersion.vramTier}
|
|
64
64
|
`
|
|
65
65
|
);
|
|
66
|
+
const brainCacheMcpEntry = {
|
|
67
|
+
command: "node",
|
|
68
|
+
args: ["node_modules/brain-cache/dist/mcp.js"]
|
|
69
|
+
};
|
|
70
|
+
const mcpJsonPath = ".mcp.json";
|
|
71
|
+
if (existsSync(mcpJsonPath)) {
|
|
72
|
+
const mcpContent = readFileSync(mcpJsonPath, "utf-8");
|
|
73
|
+
const parsed = JSON.parse(mcpContent);
|
|
74
|
+
const existing = parsed.mcpServers?.["brain-cache"];
|
|
75
|
+
if (existing && JSON.stringify(existing) === JSON.stringify(brainCacheMcpEntry)) {
|
|
76
|
+
process.stderr.write("brain-cache: .mcp.json already contains brain-cache MCP server, skipping.\n");
|
|
77
|
+
} else {
|
|
78
|
+
parsed.mcpServers = parsed.mcpServers ?? {};
|
|
79
|
+
parsed.mcpServers["brain-cache"] = brainCacheMcpEntry;
|
|
80
|
+
writeFileSync(mcpJsonPath, JSON.stringify(parsed, null, 2) + "\n");
|
|
81
|
+
process.stderr.write("brain-cache: added brain-cache MCP server to .mcp.json.\n");
|
|
82
|
+
}
|
|
83
|
+
} else {
|
|
84
|
+
const mcpConfig = { mcpServers: { "brain-cache": brainCacheMcpEntry } };
|
|
85
|
+
writeFileSync(mcpJsonPath, JSON.stringify(mcpConfig, null, 2) + "\n");
|
|
86
|
+
process.stderr.write("brain-cache: created .mcp.json with brain-cache MCP server.\n");
|
|
87
|
+
}
|
|
66
88
|
const claudeMdPath = "CLAUDE.md";
|
|
67
89
|
const brainCacheSection = `
|
|
68
90
|
## Brain-Cache MCP Tools
|