brain-cache 0.1.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.
@@ -0,0 +1,162 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ GLOBAL_CONFIG_DIR,
4
+ PROFILE_PATH,
5
+ childLogger
6
+ } from "./chunk-PDQXJSH4.js";
7
+
8
+ // src/services/capability.ts
9
+ import { execFile } from "child_process";
10
+ import { promisify } from "util";
11
+ import { readFile, writeFile, mkdir } from "fs/promises";
12
+
13
+ // src/lib/types.ts
14
+ import { z } from "zod";
15
+ var CapabilityProfileSchema = z.object({
16
+ version: z.literal(1),
17
+ detectedAt: z.string().datetime(),
18
+ vramTier: z.enum(["none", "standard", "large"]),
19
+ vramGiB: z.number().nullable(),
20
+ gpuVendor: z.enum(["nvidia", "apple", "none"]),
21
+ embeddingModel: z.string(),
22
+ ollamaVersion: z.string().nullable(),
23
+ platform: z.string()
24
+ });
25
+ var CodeChunkSchema = z.object({
26
+ id: z.string(),
27
+ filePath: z.string(),
28
+ chunkType: z.enum(["function", "class", "method", "file"]),
29
+ scope: z.string().nullable(),
30
+ name: z.string().nullable(),
31
+ content: z.string(),
32
+ startLine: z.number().int(),
33
+ endLine: z.number().int()
34
+ });
35
+ var IndexStateSchema = z.object({
36
+ version: z.literal(1),
37
+ embeddingModel: z.string(),
38
+ dimension: z.number().int(),
39
+ indexedAt: z.string().datetime(),
40
+ fileCount: z.number().int(),
41
+ chunkCount: z.number().int()
42
+ });
43
+
44
+ // src/services/capability.ts
45
+ var execFileAsync = promisify(execFile);
46
+ var log = childLogger("capability");
47
+ async function detectNvidiaVRAM() {
48
+ try {
49
+ const { stdout } = await execFileAsync("nvidia-smi", [
50
+ "--query-gpu=memory.total",
51
+ "--format=csv,noheader,nounits"
52
+ ], { timeout: 3e3 });
53
+ const raw = stdout.trim().split("\n")[0];
54
+ const mib = parseInt(raw, 10);
55
+ if (isNaN(mib)) {
56
+ log.debug({ raw }, "nvidia-smi returned non-numeric output");
57
+ return null;
58
+ }
59
+ return Math.round(mib / 1024);
60
+ } catch (err) {
61
+ log.debug({ err }, "nvidia-smi not available or timed out");
62
+ return null;
63
+ }
64
+ }
65
+ async function detectAppleSiliconVRAM() {
66
+ if (process.platform !== "darwin") {
67
+ return null;
68
+ }
69
+ try {
70
+ const { stdout } = await execFileAsync("system_profiler", [
71
+ "SPHardwareDataType",
72
+ "-json"
73
+ ], { timeout: 3e3 });
74
+ const data = JSON.parse(stdout);
75
+ const hwInfo = data.SPHardwareDataType?.[0];
76
+ if (!hwInfo) return null;
77
+ const chipType = hwInfo.chip_type ?? "";
78
+ if (!chipType.includes("Apple M")) {
79
+ log.debug({ chipType }, "Non-Apple-Silicon Mac detected, skipping VRAM detection");
80
+ return null;
81
+ }
82
+ const memoryStr = hwInfo.physical_memory ?? "";
83
+ const match = memoryStr.match(/^(\d+)\s*GB/i);
84
+ if (!match) {
85
+ log.debug({ memoryStr }, "Could not parse physical_memory from system_profiler");
86
+ return null;
87
+ }
88
+ return parseInt(match[1], 10);
89
+ } catch (err) {
90
+ log.debug({ err }, "system_profiler failed or returned invalid JSON");
91
+ return null;
92
+ }
93
+ }
94
+ function classifyVRAMTier(vramGiB) {
95
+ if (vramGiB === null || vramGiB < 2) return "none";
96
+ if (vramGiB < 8) return "standard";
97
+ return "large";
98
+ }
99
+ function selectEmbeddingModel(tier) {
100
+ switch (tier) {
101
+ case "none":
102
+ case "standard":
103
+ return "nomic-embed-text";
104
+ case "large":
105
+ return "mxbai-embed-large";
106
+ }
107
+ }
108
+ async function readProfile() {
109
+ try {
110
+ const raw = await readFile(PROFILE_PATH, "utf-8");
111
+ const json = JSON.parse(raw);
112
+ const result = CapabilityProfileSchema.safeParse(json);
113
+ if (!result.success) {
114
+ log.debug({ issues: result.error.issues }, "Profile failed schema validation");
115
+ return null;
116
+ }
117
+ return result.data;
118
+ } catch (err) {
119
+ log.debug({ err }, "Could not read profile");
120
+ return null;
121
+ }
122
+ }
123
+ async function writeProfile(profile) {
124
+ await mkdir(GLOBAL_CONFIG_DIR, { recursive: true });
125
+ await writeFile(PROFILE_PATH, JSON.stringify(profile, null, 2));
126
+ log.debug({ path: PROFILE_PATH }, "Profile written");
127
+ }
128
+ async function detectCapabilities() {
129
+ let vramGiB = null;
130
+ let gpuVendor = "none";
131
+ const nvidiaVram = await detectNvidiaVRAM();
132
+ if (nvidiaVram !== null) {
133
+ vramGiB = nvidiaVram;
134
+ gpuVendor = "nvidia";
135
+ } else {
136
+ const appleVram = await detectAppleSiliconVRAM();
137
+ if (appleVram !== null) {
138
+ vramGiB = appleVram;
139
+ gpuVendor = "apple";
140
+ }
141
+ }
142
+ const vramTier = classifyVRAMTier(vramGiB);
143
+ const embeddingModel = selectEmbeddingModel(vramTier);
144
+ log.info({ gpuVendor, vramGiB, vramTier, embeddingModel }, "Hardware capabilities detected");
145
+ return {
146
+ version: 1,
147
+ detectedAt: (/* @__PURE__ */ new Date()).toISOString(),
148
+ vramTier,
149
+ vramGiB,
150
+ gpuVendor,
151
+ embeddingModel,
152
+ ollamaVersion: null,
153
+ platform: process.platform
154
+ };
155
+ }
156
+
157
+ export {
158
+ IndexStateSchema,
159
+ readProfile,
160
+ writeProfile,
161
+ detectCapabilities
162
+ };
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/lib/config.ts
4
+ import { homedir } from "os";
5
+ import { join } from "path";
6
+ var GLOBAL_CONFIG_DIR = join(homedir(), ".brain-cache");
7
+ var PROFILE_PATH = join(GLOBAL_CONFIG_DIR, "profile.json");
8
+ var CONFIG_PATH = join(GLOBAL_CONFIG_DIR, "config.json");
9
+ var PROJECT_DATA_DIR = ".brain-cache";
10
+ var EMBEDDING_DIMENSIONS = {
11
+ "nomic-embed-text": 768,
12
+ "mxbai-embed-large": 1024
13
+ };
14
+ var DEFAULT_EMBEDDING_DIMENSION = 768;
15
+ var DEFAULT_BATCH_SIZE = 50;
16
+ var FILE_READ_CONCURRENCY = 20;
17
+ var VECTOR_INDEX_THRESHOLD = 256;
18
+ var EMBED_TIMEOUT_MS = 3e4;
19
+ var COLD_START_RETRY_DELAY_MS = 2e3;
20
+ var EMBED_MAX_TOKENS = 8192;
21
+ var DEFAULT_SEARCH_LIMIT = 10;
22
+ var DEFAULT_DISTANCE_THRESHOLD = 0.4;
23
+ var DIAGNOSTIC_DISTANCE_THRESHOLD = 0.45;
24
+ var DIAGNOSTIC_SEARCH_LIMIT = 20;
25
+ var DEFAULT_TOKEN_BUDGET = 4096;
26
+ var FILE_HASHES_FILENAME = "file-hashes.json";
27
+
28
+ // src/services/logger.ts
29
+ import pino from "pino";
30
+ var VALID_LEVELS = ["debug", "info", "warn", "error", "silent"];
31
+ function resolveLevel() {
32
+ const env = process.env.BRAIN_CACHE_LOG?.toLowerCase();
33
+ if (VALID_LEVELS.includes(env)) return env;
34
+ return "warn";
35
+ }
36
+ var logger = pino(
37
+ {
38
+ level: resolveLevel(),
39
+ redact: {
40
+ paths: [
41
+ "apiKey",
42
+ "api_key",
43
+ "secret",
44
+ "password",
45
+ "token",
46
+ "authorization",
47
+ "ANTHROPIC_API_KEY",
48
+ "OPENAI_API_KEY",
49
+ "*.apiKey",
50
+ "*.api_key",
51
+ "*.secret",
52
+ "*.password",
53
+ "*.token",
54
+ "*.authorization",
55
+ "*.ANTHROPIC_API_KEY",
56
+ "*.OPENAI_API_KEY"
57
+ ],
58
+ censor: "[Redacted]"
59
+ }
60
+ },
61
+ pino.destination(2)
62
+ // stderr, always — per D-16
63
+ );
64
+ function childLogger(component) {
65
+ return logger.child({ component });
66
+ }
67
+
68
+ export {
69
+ GLOBAL_CONFIG_DIR,
70
+ PROFILE_PATH,
71
+ PROJECT_DATA_DIR,
72
+ EMBEDDING_DIMENSIONS,
73
+ DEFAULT_EMBEDDING_DIMENSION,
74
+ DEFAULT_BATCH_SIZE,
75
+ FILE_READ_CONCURRENCY,
76
+ VECTOR_INDEX_THRESHOLD,
77
+ EMBED_TIMEOUT_MS,
78
+ COLD_START_RETRY_DELAY_MS,
79
+ EMBED_MAX_TOKENS,
80
+ DEFAULT_SEARCH_LIMIT,
81
+ DEFAULT_DISTANCE_THRESHOLD,
82
+ DIAGNOSTIC_DISTANCE_THRESHOLD,
83
+ DIAGNOSTIC_SEARCH_LIMIT,
84
+ DEFAULT_TOKEN_BUDGET,
85
+ FILE_HASHES_FILENAME,
86
+ childLogger
87
+ };
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ COLD_START_RETRY_DELAY_MS,
4
+ DEFAULT_EMBEDDING_DIMENSION,
5
+ EMBED_TIMEOUT_MS,
6
+ childLogger
7
+ } from "./chunk-PDQXJSH4.js";
8
+
9
+ // src/services/embedder.ts
10
+ import ollama from "ollama";
11
+ var log = childLogger("embedder");
12
+ async function embedBatch(model, texts, timeoutMs = EMBED_TIMEOUT_MS) {
13
+ if (texts.length === 0) {
14
+ return [];
15
+ }
16
+ log.debug({ model, batchSize: texts.length }, "Embedding batch");
17
+ const embedCall = ollama.embed({ model, input: texts, truncate: true }).then((r) => r.embeddings);
18
+ let timerId;
19
+ const timeoutPromise = new Promise((_, reject) => {
20
+ timerId = setTimeout(
21
+ () => reject(new Error(`Embed timeout after ${timeoutMs}ms`)),
22
+ timeoutMs
23
+ );
24
+ });
25
+ timeoutPromise.catch(() => {
26
+ });
27
+ try {
28
+ return await Promise.race([embedCall, timeoutPromise]);
29
+ } finally {
30
+ clearTimeout(timerId);
31
+ }
32
+ }
33
+ function isConnectionError(err) {
34
+ const msg = err instanceof Error ? err.message.toLowerCase() : String(err).toLowerCase();
35
+ return msg.includes("econnreset") || msg.includes("econnrefused") || msg.includes("fetch failed") || msg.includes("socket hang up");
36
+ }
37
+ function isContextLengthError(err) {
38
+ const msg = err instanceof Error ? err.message.toLowerCase() : String(err).toLowerCase();
39
+ return msg.includes("input length exceeds the context length");
40
+ }
41
+ async function embedBatchWithRetry(model, texts, dimension = DEFAULT_EMBEDDING_DIMENSION, attempt = 0) {
42
+ try {
43
+ return await embedBatch(model, texts);
44
+ } catch (err) {
45
+ if (attempt === 0 && isConnectionError(err)) {
46
+ log.warn({ model }, "Ollama cold-start suspected, retrying in 5s");
47
+ await new Promise((r) => setTimeout(r, COLD_START_RETRY_DELAY_MS));
48
+ return embedBatchWithRetry(model, texts, dimension, 1);
49
+ }
50
+ if (isContextLengthError(err)) {
51
+ log.warn({ model, batchSize: texts.length }, "Batch exceeded context length, falling back to individual embedding");
52
+ const results = [];
53
+ for (const text of texts) {
54
+ try {
55
+ const [vec] = await embedBatch(model, [text]);
56
+ results.push(vec);
57
+ } catch (innerErr) {
58
+ if (isContextLengthError(innerErr)) {
59
+ process.stderr.write(
60
+ `
61
+ brain-cache: chunk too large for embedding model, skipping (${text.length} chars)
62
+ `
63
+ );
64
+ results.push(new Array(dimension).fill(0));
65
+ } else {
66
+ throw innerErr;
67
+ }
68
+ }
69
+ }
70
+ return results;
71
+ }
72
+ throw err;
73
+ }
74
+ }
75
+
76
+ export {
77
+ embedBatch,
78
+ embedBatchWithRetry
79
+ };
@@ -0,0 +1,151 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ IndexStateSchema
4
+ } from "./chunk-PA4BZBWS.js";
5
+ import {
6
+ DEFAULT_EMBEDDING_DIMENSION,
7
+ EMBEDDING_DIMENSIONS,
8
+ FILE_HASHES_FILENAME,
9
+ PROJECT_DATA_DIR,
10
+ VECTOR_INDEX_THRESHOLD,
11
+ childLogger
12
+ } from "./chunk-PDQXJSH4.js";
13
+
14
+ // src/services/lancedb.ts
15
+ import * as lancedb from "@lancedb/lancedb";
16
+ import { Index } from "@lancedb/lancedb";
17
+ import { Schema, Field, Utf8, Int32, Float32, FixedSizeList } from "apache-arrow";
18
+ import { join } from "path";
19
+ import { readFile, writeFile, mkdir } from "fs/promises";
20
+ var log = childLogger("lancedb");
21
+ function chunkSchema(dim) {
22
+ return new Schema([
23
+ new Field("id", new Utf8(), false),
24
+ new Field("file_path", new Utf8(), false),
25
+ new Field("chunk_type", new Utf8(), false),
26
+ new Field("scope", new Utf8(), true),
27
+ new Field("name", new Utf8(), true),
28
+ new Field("content", new Utf8(), false),
29
+ new Field("start_line", new Int32(), false),
30
+ new Field("end_line", new Int32(), false),
31
+ new Field(
32
+ "vector",
33
+ new FixedSizeList(dim, new Field("item", new Float32(), true)),
34
+ false
35
+ )
36
+ ]);
37
+ }
38
+ async function openDatabase(projectRoot) {
39
+ const dataDir = join(projectRoot, PROJECT_DATA_DIR);
40
+ await mkdir(dataDir, { recursive: true });
41
+ const dbPath = join(dataDir, "index");
42
+ return lancedb.connect(dbPath);
43
+ }
44
+ async function openOrCreateChunkTable(db, projectRoot, model, dim) {
45
+ const tableNames = await db.tableNames();
46
+ if (tableNames.includes("chunks")) {
47
+ const state = await readIndexState(projectRoot);
48
+ const mismatch = state === null || state.embeddingModel !== model || state.dimension !== dim;
49
+ if (mismatch) {
50
+ log.warn(
51
+ { storedModel: state?.embeddingModel, storedDim: state?.dimension, model, dim },
52
+ "Embedding model or dimension changed \u2014 dropping and recreating chunks table"
53
+ );
54
+ await db.dropTable("chunks");
55
+ } else {
56
+ log.info({ model, dim }, "Opened existing chunks table");
57
+ return db.openTable("chunks");
58
+ }
59
+ }
60
+ const schema = chunkSchema(dim);
61
+ const emptyData = lancedb.makeArrowTable([], { schema });
62
+ const table = await db.createTable("chunks", emptyData, { mode: "overwrite" });
63
+ log.info({ model, dim }, "Created new chunks table");
64
+ return table;
65
+ }
66
+ async function insertChunks(table, rows) {
67
+ if (rows.length === 0) {
68
+ return;
69
+ }
70
+ await table.add(rows);
71
+ log.debug({ count: rows.length }, "Inserted chunk rows");
72
+ }
73
+ async function createVectorIndexIfNeeded(table, embeddingModel) {
74
+ const rowCount = await table.countRows();
75
+ if (rowCount < VECTOR_INDEX_THRESHOLD) {
76
+ log.debug(
77
+ { rowCount, threshold: VECTOR_INDEX_THRESHOLD },
78
+ "Row count below threshold \u2014 skipping IVF-PQ index creation"
79
+ );
80
+ return;
81
+ }
82
+ const indices = await table.listIndices();
83
+ const hasVectorIndex = indices.some(
84
+ (idx) => idx.columns.includes("vector")
85
+ );
86
+ if (hasVectorIndex) {
87
+ log.debug("IVF-PQ index already exists \u2014 skipping creation");
88
+ return;
89
+ }
90
+ const dim = EMBEDDING_DIMENSIONS[embeddingModel] ?? DEFAULT_EMBEDDING_DIMENSION;
91
+ const numSubVectors = Math.floor(dim / 8);
92
+ log.info(
93
+ { rowCount, numPartitions: 256, numSubVectors },
94
+ "Creating IVF-PQ vector index"
95
+ );
96
+ await table.createIndex("vector", {
97
+ config: Index.ivfPq({ numPartitions: 256, numSubVectors })
98
+ });
99
+ log.info("IVF-PQ vector index created successfully");
100
+ }
101
+ async function readIndexState(projectRoot) {
102
+ const statePath = join(projectRoot, PROJECT_DATA_DIR, "index_state.json");
103
+ try {
104
+ const raw = await readFile(statePath, "utf-8");
105
+ const parsed = IndexStateSchema.safeParse(JSON.parse(raw));
106
+ return parsed.success ? parsed.data : null;
107
+ } catch {
108
+ return null;
109
+ }
110
+ }
111
+ async function writeIndexState(projectRoot, state) {
112
+ const dataDir = join(projectRoot, PROJECT_DATA_DIR);
113
+ await mkdir(dataDir, { recursive: true });
114
+ const statePath = join(dataDir, "index_state.json");
115
+ await writeFile(statePath, JSON.stringify(state, null, 2), "utf-8");
116
+ }
117
+ async function readFileHashes(projectRoot) {
118
+ const hashPath = join(projectRoot, PROJECT_DATA_DIR, FILE_HASHES_FILENAME);
119
+ try {
120
+ const raw = await readFile(hashPath, "utf-8");
121
+ const parsed = JSON.parse(raw);
122
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
123
+ return parsed;
124
+ }
125
+ return {};
126
+ } catch {
127
+ return {};
128
+ }
129
+ }
130
+ async function writeFileHashes(projectRoot, hashes) {
131
+ const dataDir = join(projectRoot, PROJECT_DATA_DIR);
132
+ await mkdir(dataDir, { recursive: true });
133
+ const hashPath = join(dataDir, FILE_HASHES_FILENAME);
134
+ await writeFile(hashPath, JSON.stringify(hashes, null, 2), "utf-8");
135
+ }
136
+ async function deleteChunksByFilePath(table, filePath) {
137
+ const escaped = filePath.replace(/'/g, "''");
138
+ await table.delete(`file_path = '${escaped}'`);
139
+ }
140
+
141
+ export {
142
+ openDatabase,
143
+ openOrCreateChunkTable,
144
+ insertChunks,
145
+ createVectorIndexIfNeeded,
146
+ readIndexState,
147
+ writeIndexState,
148
+ readFileHashes,
149
+ writeFileHashes,
150
+ deleteChunksByFilePath
151
+ };
@@ -0,0 +1,109 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ DEFAULT_DISTANCE_THRESHOLD,
4
+ DEFAULT_SEARCH_LIMIT,
5
+ DIAGNOSTIC_DISTANCE_THRESHOLD,
6
+ DIAGNOSTIC_SEARCH_LIMIT,
7
+ childLogger
8
+ } from "./chunk-PDQXJSH4.js";
9
+
10
+ // src/services/retriever.ts
11
+ var log = childLogger("retriever");
12
+ var DIAGNOSTIC_KEYWORDS = [
13
+ "why",
14
+ "broken",
15
+ "error",
16
+ "bug",
17
+ "fail",
18
+ "crash",
19
+ "exception",
20
+ "undefined",
21
+ "null",
22
+ "wrong",
23
+ "issue",
24
+ "problem",
25
+ "causes",
26
+ "caused",
27
+ "debug",
28
+ "fix",
29
+ "incorrect",
30
+ "unexpected"
31
+ ];
32
+ var DIAGNOSTIC_BIGRAMS = [
33
+ "stack trace",
34
+ "null pointer",
35
+ "not defined",
36
+ "type error",
37
+ "reference error",
38
+ "syntax error",
39
+ "runtime error",
40
+ "segmentation fault",
41
+ "not working",
42
+ "throws exception"
43
+ ];
44
+ var DIAGNOSTIC_EXCLUSIONS = [
45
+ "error handler",
46
+ "error handling",
47
+ "error boundary",
48
+ "error type",
49
+ "error message",
50
+ "error code",
51
+ "error class",
52
+ "null object",
53
+ "null check",
54
+ "null pattern",
55
+ "undefined behavior",
56
+ "fix the style",
57
+ "fix the format",
58
+ "fix the lint",
59
+ "fix the config",
60
+ "fix the setup"
61
+ ];
62
+ function classifyQueryIntent(query) {
63
+ const lower = query.toLowerCase();
64
+ if (DIAGNOSTIC_BIGRAMS.some((bg) => lower.includes(bg))) {
65
+ return "diagnostic";
66
+ }
67
+ const hasKeyword = DIAGNOSTIC_KEYWORDS.some((kw) => lower.includes(kw));
68
+ if (hasKeyword) {
69
+ const isExcluded = DIAGNOSTIC_EXCLUSIONS.some((ex) => lower.includes(ex));
70
+ if (!isExcluded) {
71
+ return "diagnostic";
72
+ }
73
+ }
74
+ return "knowledge";
75
+ }
76
+ var RETRIEVAL_STRATEGIES = {
77
+ diagnostic: { limit: DIAGNOSTIC_SEARCH_LIMIT, distanceThreshold: DIAGNOSTIC_DISTANCE_THRESHOLD },
78
+ knowledge: { limit: DEFAULT_SEARCH_LIMIT, distanceThreshold: DEFAULT_DISTANCE_THRESHOLD }
79
+ };
80
+ async function searchChunks(table, queryVector, opts) {
81
+ log.debug({ limit: opts.limit, distanceThreshold: opts.distanceThreshold }, "Searching chunks");
82
+ const rows = await table.query().nearestTo(queryVector).distanceType("cosine").limit(opts.limit).toArray();
83
+ return rows.filter((r) => r._distance <= opts.distanceThreshold).map((r) => ({
84
+ id: r.id,
85
+ filePath: r.file_path,
86
+ chunkType: r.chunk_type,
87
+ scope: r.scope,
88
+ name: r.name,
89
+ content: r.content,
90
+ startLine: r.start_line,
91
+ endLine: r.end_line,
92
+ similarity: 1 - r._distance
93
+ })).sort((a, b) => b.similarity - a.similarity);
94
+ }
95
+ function deduplicateChunks(chunks) {
96
+ const seen = /* @__PURE__ */ new Set();
97
+ return chunks.filter((c) => {
98
+ if (seen.has(c.id)) return false;
99
+ seen.add(c.id);
100
+ return true;
101
+ });
102
+ }
103
+
104
+ export {
105
+ classifyQueryIntent,
106
+ RETRIEVAL_STRATEGIES,
107
+ searchChunks,
108
+ deduplicateChunks
109
+ };
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+
2
+ export { }
package/dist/cli.js ADDED
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ formatTokenSavings
4
+ } from "./chunk-GGOUKACO.js";
5
+
6
+ // src/cli/index.ts
7
+ import { Command } from "commander";
8
+ var version = "0.1.0";
9
+ var program = new Command();
10
+ program.name("brain-cache").description("Local AI runtime \u2014 GPU cache layer for Claude").version(version);
11
+ program.command("init").description("Detect hardware, pull embedding model, create config directory").action(async () => {
12
+ const { runInit } = await import("./init-TRPFEOHF.js");
13
+ await runInit();
14
+ });
15
+ program.command("doctor").description("Report system health: GPU, VRAM tier, Ollama status").action(async () => {
16
+ const { runDoctor } = await import("./doctor-5775VUMA.js");
17
+ await runDoctor();
18
+ });
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");
21
+ await runIndex(path, { force: opts.force });
22
+ });
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");
25
+ await runSearch(query, {
26
+ limit: parseInt(opts.limit, 10),
27
+ path: opts.path
28
+ });
29
+ });
30
+ program.command("status").description(
31
+ "Show index stats: files indexed, chunks stored, last indexed time"
32
+ ).argument("[path]", "Project root directory (defaults to current directory)").action(async (path) => {
33
+ const { runStatus } = await import("./status-2SOIQ3LX.js");
34
+ await runStatus(path);
35
+ });
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
+ async (query, opts) => {
38
+ const { runBuildContext } = await import("./buildContext-6755TRND.js");
39
+ const result = await runBuildContext(query, {
40
+ limit: parseInt(opts.limit, 10),
41
+ maxTokens: parseInt(opts.budget, 10),
42
+ path: opts.path
43
+ });
44
+ if (opts.raw) {
45
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
46
+ } else {
47
+ process.stdout.write(result.content);
48
+ if (!result.content.endsWith("\n")) {
49
+ process.stdout.write("\n");
50
+ }
51
+ const estimatedWithout = result.metadata.reductionPct > 0 ? Math.round(result.metadata.tokensSent / (1 - result.metadata.reductionPct / 100)) : result.metadata.tokensSent;
52
+ process.stderr.write(
53
+ `
54
+ \u{1F9E0} brain-cache
55
+ ${formatTokenSavings({ tokensSent: result.metadata.tokensSent, estimatedWithout, reductionPct: result.metadata.reductionPct })}
56
+ `
57
+ );
58
+ }
59
+ }
60
+ );
61
+ program.command("ask").description(
62
+ "Ask a natural language question about the codebase \u2014 retrieves context locally, reasons via Claude"
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");
65
+ const result = await runAskCodebase(question, {
66
+ path: opts.path,
67
+ maxContextTokens: parseInt(opts.budget, 10)
68
+ });
69
+ process.stderr.write(`
70
+ ${result.answer}
71
+ `);
72
+ process.stderr.write(
73
+ `
74
+ \u{1F9E0} brain-cache
75
+ ${formatTokenSavings({ tokensSent: result.contextMetadata.tokensSent, estimatedWithout: result.contextMetadata.estimatedWithoutBraincache, reductionPct: result.contextMetadata.reductionPct })}
76
+ `
77
+ );
78
+ });
79
+ (async () => {
80
+ await program.parseAsync();
81
+ })().catch((err) => {
82
+ const message = err instanceof Error ? err.message : String(err);
83
+ process.stderr.write(`Error: ${message}
84
+ `);
85
+ process.exit(1);
86
+ });