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 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. Once configured, Claude automatically calls brain-cache tools instead of reading raw files no prompting required.
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` appends MCP tool instructions to your `CLAUDE.md` so Claude knows when and how to prefer brain-cache tools over built-in file reading. Runs once; won’t duplicate.
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
- ```json
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-7JLSJNKU.js";
8
- import "./chunk-OKWMQNH6.js";
9
- import "./chunk-ZLB4VJQK.js";
10
- import "./chunk-WCNMLSL2.js";
11
- import "./chunk-P7WSTGLE.js";
12
- import "./chunk-XXWJ57QP.js";
13
- import "./chunk-PA4BZBWS.js";
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-PDQXJSH4.js";
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
  childLogger
4
- } from "./chunk-PDQXJSH4.js";
4
+ } from "./chunk-3SFDFUEX.js";
5
5
 
6
6
  // src/services/tokenCounter.ts
7
7
  import { countTokens } from "@anthropic-ai/tokenizer";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  IndexStateSchema
4
- } from "./chunk-PA4BZBWS.js";
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-PDQXJSH4.js";
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-OKWMQNH6.js";
5
+ } from "./chunk-5FXXZBZV.js";
6
6
  import {
7
7
  RETRIEVAL_STRATEGIES,
8
8
  classifyQueryIntent,
9
9
  deduplicateChunks,
10
10
  searchChunks
11
- } from "./chunk-ZLB4VJQK.js";
11
+ } from "./chunk-BF5UDEIF.js";
12
12
  import {
13
13
  embedBatchWithRetry
14
- } from "./chunk-WCNMLSL2.js";
14
+ } from "./chunk-GR6QXZ4J.js";
15
15
  import {
16
16
  isOllamaRunning
17
- } from "./chunk-P7WSTGLE.js";
17
+ } from "./chunk-V4ARVFRG.js";
18
18
  import {
19
19
  openDatabase,
20
20
  readIndexState
21
- } from "./chunk-XXWJ57QP.js";
21
+ } from "./chunk-6MACVOTO.js";
22
22
  import {
23
23
  readProfile
24
- } from "./chunk-PA4BZBWS.js";
24
+ } from "./chunk-MSI4MDIM.js";
25
25
  import {
26
26
  DEFAULT_TOKEN_BUDGET
27
- } from "./chunk-PDQXJSH4.js";
27
+ } from "./chunk-3SFDFUEX.js";
28
28
 
29
29
  // src/workflows/buildContext.ts
30
30
  import { readFile } from "fs/promises";
@@ -5,7 +5,7 @@ import {
5
5
  DIAGNOSTIC_DISTANCE_THRESHOLD,
6
6
  DIAGNOSTIC_SEARCH_LIMIT,
7
7
  childLogger
8
- } from "./chunk-PDQXJSH4.js";
8
+ } from "./chunk-3SFDFUEX.js";
9
9
 
10
10
  // src/services/retriever.ts
11
11
  var log = childLogger("retriever");
@@ -4,7 +4,7 @@ import {
4
4
  DEFAULT_EMBEDDING_DIMENSION,
5
5
  EMBED_TIMEOUT_MS,
6
6
  childLogger
7
- } from "./chunk-PDQXJSH4.js";
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
- return await embedBatch(model, texts);
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
- process.stderr.write(
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
  }
@@ -3,7 +3,7 @@ import {
3
3
  GLOBAL_CONFIG_DIR,
4
4
  PROFILE_PATH,
5
5
  childLogger
6
- } from "./chunk-PDQXJSH4.js";
6
+ } from "./chunk-3SFDFUEX.js";
7
7
 
8
8
  // src/services/capability.ts
9
9
  import { execFile } from "child_process";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  childLogger
4
- } from "./chunk-PDQXJSH4.js";
4
+ } from "./chunk-3SFDFUEX.js";
5
5
 
6
6
  // src/services/ollama.ts
7
7
  import { execFile, spawn } from "child_process";
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.1.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-TRPFEOHF.js");
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-5775VUMA.js");
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-MJLEPCZY.js");
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-WKKGPNLV.js");
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-2SOIQ3LX.js");
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-6755TRND.js");
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-ECDSSTQ6.js");
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-P7WSTGLE.js";
7
+ } from "./chunk-V4ARVFRG.js";
8
8
  import {
9
9
  detectCapabilities,
10
10
  readProfile
11
- } from "./chunk-PA4BZBWS.js";
11
+ } from "./chunk-MSI4MDIM.js";
12
12
  import {
13
13
  PROFILE_PATH
14
- } from "./chunk-PDQXJSH4.js";
14
+ } from "./chunk-3SFDFUEX.js";
15
15
 
16
16
  // src/workflows/doctor.ts
17
17
  import ollama from "ollama";
@@ -2,8 +2,8 @@
2
2
  import {
3
3
  embedBatch,
4
4
  embedBatchWithRetry
5
- } from "./chunk-WCNMLSL2.js";
6
- import "./chunk-PDQXJSH4.js";
5
+ } from "./chunk-GR6QXZ4J.js";
6
+ import "./chunk-3SFDFUEX.js";
7
7
  export {
8
8
  embedBatch,
9
9
  embedBatchWithRetry
@@ -5,12 +5,12 @@ import {
5
5
  isOllamaRunning,
6
6
  pullModelIfMissing,
7
7
  startOllama
8
- } from "./chunk-P7WSTGLE.js";
8
+ } from "./chunk-V4ARVFRG.js";
9
9
  import {
10
10
  detectCapabilities,
11
11
  writeProfile
12
- } from "./chunk-PA4BZBWS.js";
13
- import "./chunk-PDQXJSH4.js";
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-KRANITVN.js");
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