brainbank 0.7.0 → 0.8.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 +76 -1398
- package/bin/brainbank +5 -1
- package/dist/{chunk-N2OJRXSB.js → chunk-3HVCONGF.js} +1 -1
- package/dist/{chunk-N2OJRXSB.js.map → chunk-3HVCONGF.js.map} +1 -1
- package/dist/{chunk-CCXVL56V.js → chunk-3JZIM5AU.js} +6 -3
- package/dist/chunk-3JZIM5AU.js.map +1 -0
- package/dist/{chunk-6XOXM7MI.js → chunk-5KU2PP34.js} +2 -2
- package/dist/{chunk-6XOXM7MI.js.map → chunk-5KU2PP34.js.map} +1 -1
- package/dist/chunk-7JDCHUJV.js +89 -0
- package/dist/chunk-7JDCHUJV.js.map +1 -0
- package/dist/{chunk-B77KABWH.js → chunk-7T2ZCZQA.js} +17 -15
- package/dist/chunk-7T2ZCZQA.js.map +1 -0
- package/dist/chunk-E3J37GDA.js +74 -0
- package/dist/chunk-E3J37GDA.js.map +1 -0
- package/dist/chunk-JEFWMS5Z.js +217 -0
- package/dist/chunk-JEFWMS5Z.js.map +1 -0
- package/dist/chunk-JRVYSTMP.js +3256 -0
- package/dist/chunk-JRVYSTMP.js.map +1 -0
- package/dist/chunk-OLDHLOMT.js +69 -0
- package/dist/chunk-OLDHLOMT.js.map +1 -0
- package/dist/{chunk-424UFCY7.js → chunk-QTZNB6AK.js} +6 -2
- package/dist/chunk-QTZNB6AK.js.map +1 -0
- package/dist/chunk-RFF7HMP6.js +109 -0
- package/dist/chunk-RFF7HMP6.js.map +1 -0
- package/dist/{chunk-ZNLN2VWV.js → chunk-TD3TEFI3.js} +1 -1
- package/dist/chunk-TD3TEFI3.js.map +1 -0
- package/dist/cli.js +1036 -341
- package/dist/cli.js.map +1 -1
- package/dist/haiku-expander-WOVJIVXD.js +8 -0
- package/dist/haiku-pruner-DB77ZQLJ.js +8 -0
- package/dist/http-server-GIRELCCL.js +9 -0
- package/dist/index.d.ts +1774 -611
- package/dist/index.js +282 -70
- package/dist/index.js.map +1 -1
- package/dist/{local-embedding-ZIMTK6PU.js → local-embedding-2RNCC5EU.js} +2 -2
- package/dist/{openai-embedding-VQZCZQYT.js → openai-embedding-Z5I4K4CN.js} +2 -2
- package/dist/perplexity-context-embedding-V5YUMXDR.js +9 -0
- package/dist/{perplexity-embedding-227WQY4R.js → perplexity-embedding-X2S72OAC.js} +2 -2
- package/dist/plugin-FF4Q34TI.js +32 -0
- package/dist/{qwen3-reranker-3MHEENT5.js → qwen3-reranker-HVIQOLKS.js} +2 -2
- package/dist/{resolve-CUJWY6HP.js → resolve-Q5D6HECY.js} +2 -2
- package/package.json +25 -52
- package/src/brainbank.ts +620 -0
- package/src/cli/commands/collection.ts +77 -0
- package/src/cli/commands/context.ts +171 -0
- package/src/cli/commands/daemon.ts +100 -0
- package/src/cli/commands/docs.ts +71 -0
- package/src/cli/commands/files.ts +69 -0
- package/src/cli/commands/help.ts +72 -0
- package/src/cli/commands/index.ts +282 -0
- package/src/cli/commands/kv.ts +140 -0
- package/src/cli/commands/mcp.ts +13 -0
- package/src/cli/commands/reembed.ts +30 -0
- package/src/cli/commands/scan.ts +365 -0
- package/src/cli/commands/search.ts +130 -0
- package/src/cli/commands/stats.ts +44 -0
- package/src/cli/commands/status.ts +47 -0
- package/src/cli/commands/watch.ts +43 -0
- package/src/cli/factory/brain-context.ts +43 -0
- package/src/cli/factory/builtin-registration.ts +123 -0
- package/src/cli/factory/config-loader.ts +72 -0
- package/src/cli/factory/index.ts +65 -0
- package/src/cli/factory/plugin-loader.ts +146 -0
- package/src/cli/index.ts +63 -0
- package/src/cli/server-client.ts +135 -0
- package/src/cli/utils.ts +121 -0
- package/src/config.ts +50 -0
- package/src/constants.ts +13 -0
- package/src/db/adapter.ts +112 -0
- package/src/db/metadata.ts +130 -0
- package/src/db/migrations.ts +66 -0
- package/src/db/sqlite-adapter.ts +208 -0
- package/src/db/tracker.ts +91 -0
- package/src/engine/index-api.ts +85 -0
- package/src/engine/reembed.ts +206 -0
- package/src/engine/search-api.ts +222 -0
- package/src/index.ts +159 -0
- package/src/lib/fts.ts +57 -0
- package/src/lib/languages.ts +180 -0
- package/src/lib/logger.ts +125 -0
- package/src/lib/math.ts +87 -0
- package/src/lib/provider-key.ts +20 -0
- package/src/lib/prune.ts +71 -0
- package/src/lib/rerank.ts +33 -0
- package/src/lib/rrf.ts +133 -0
- package/src/lib/write-lock.ts +108 -0
- package/src/plugin.ts +323 -0
- package/src/providers/embeddings/embedding-worker-thread.ts +95 -0
- package/src/providers/embeddings/embedding-worker.ts +141 -0
- package/src/providers/embeddings/local-embedding.ts +115 -0
- package/src/providers/embeddings/openai-embedding.ts +167 -0
- package/src/providers/embeddings/perplexity-context-embedding.ts +195 -0
- package/src/providers/embeddings/perplexity-embedding.ts +165 -0
- package/src/providers/embeddings/resolve.ts +34 -0
- package/src/providers/pruners/haiku-expander.ts +152 -0
- package/src/providers/pruners/haiku-pruner.ts +112 -0
- package/src/providers/rerankers/qwen3-reranker.ts +180 -0
- package/src/providers/vector/hnsw-index.ts +174 -0
- package/src/providers/vector/hnsw-loader.ts +129 -0
- package/src/search/bm25-boost.ts +61 -0
- package/src/search/context-builder.ts +298 -0
- package/src/search/keyword/composite-bm25-search.ts +62 -0
- package/src/search/types.ts +35 -0
- package/src/search/vector/composite-vector-search.ts +76 -0
- package/src/search/vector/mmr.ts +64 -0
- package/src/services/collection.ts +405 -0
- package/src/services/daemon.ts +87 -0
- package/src/services/http-server.ts +288 -0
- package/src/services/kv-service.ts +65 -0
- package/src/services/plugin-registry.ts +109 -0
- package/src/services/watch.ts +348 -0
- package/src/services/webhook-server.ts +100 -0
- package/src/types.ts +504 -0
- package/dist/base-3SNc_CeY.d.ts +0 -593
- package/dist/chunk-424UFCY7.js.map +0 -1
- package/dist/chunk-7EZR47JV.js +0 -232
- package/dist/chunk-7EZR47JV.js.map +0 -1
- package/dist/chunk-B77KABWH.js.map +0 -1
- package/dist/chunk-CCXVL56V.js.map +0 -1
- package/dist/chunk-DI3H6JVZ.js +0 -2432
- package/dist/chunk-DI3H6JVZ.js.map +0 -1
- package/dist/chunk-FGL32LUJ.js +0 -754
- package/dist/chunk-FGL32LUJ.js.map +0 -1
- package/dist/chunk-JRSKWF6K.js +0 -313
- package/dist/chunk-JRSKWF6K.js.map +0 -1
- package/dist/chunk-U2Q2XGPZ.js +0 -42
- package/dist/chunk-U2Q2XGPZ.js.map +0 -1
- package/dist/chunk-VQ27YUHH.js +0 -629
- package/dist/chunk-VQ27YUHH.js.map +0 -1
- package/dist/chunk-VVXYZIIB.js +0 -304
- package/dist/chunk-VVXYZIIB.js.map +0 -1
- package/dist/chunk-YOLKSYWK.js +0 -79
- package/dist/chunk-YOLKSYWK.js.map +0 -1
- package/dist/chunk-ZNLN2VWV.js.map +0 -1
- package/dist/code.d.ts +0 -33
- package/dist/code.js +0 -9
- package/dist/docs.d.ts +0 -21
- package/dist/docs.js +0 -9
- package/dist/git.d.ts +0 -33
- package/dist/git.js +0 -9
- package/dist/memory.d.ts +0 -17
- package/dist/memory.js +0 -9
- package/dist/notes.d.ts +0 -17
- package/dist/notes.js +0 -10
- package/dist/perplexity-context-embedding-KSVSZXMD.js +0 -9
- package/dist/resolve-CUJWY6HP.js.map +0 -1
- /package/dist/{code.js.map → haiku-expander-WOVJIVXD.js.map} +0 -0
- /package/dist/{docs.js.map → haiku-pruner-DB77ZQLJ.js.map} +0 -0
- /package/dist/{git.js.map → http-server-GIRELCCL.js.map} +0 -0
- /package/dist/{local-embedding-ZIMTK6PU.js.map → local-embedding-2RNCC5EU.js.map} +0 -0
- /package/dist/{memory.js.map → openai-embedding-Z5I4K4CN.js.map} +0 -0
- /package/dist/{notes.js.map → perplexity-context-embedding-V5YUMXDR.js.map} +0 -0
- /package/dist/{openai-embedding-VQZCZQYT.js.map → perplexity-embedding-X2S72OAC.js.map} +0 -0
- /package/dist/{perplexity-context-embedding-KSVSZXMD.js.map → plugin-FF4Q34TI.js.map} +0 -0
- /package/dist/{perplexity-embedding-227WQY4R.js.map → qwen3-reranker-HVIQOLKS.js.map} +0 -0
- /package/dist/{qwen3-reranker-3MHEENT5.js.map → resolve-Q5D6HECY.js.map} +0 -0
package/dist/cli.js
CHANGED
|
@@ -1,314 +1,392 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
SUPPORTED_EXTENSIONS,
|
|
4
|
+
args,
|
|
5
|
+
c,
|
|
6
|
+
createBrain,
|
|
7
|
+
findDocsPlugin,
|
|
8
|
+
getConfig,
|
|
9
|
+
getFlag,
|
|
10
|
+
hasFlag,
|
|
11
|
+
isIgnoredDir,
|
|
12
|
+
isIgnoredFile,
|
|
13
|
+
loadConfig,
|
|
14
|
+
printResults,
|
|
15
|
+
registerConfigCollections,
|
|
16
|
+
stripFlags
|
|
17
|
+
} from "./chunk-JRVYSTMP.js";
|
|
6
18
|
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
import
|
|
13
|
-
docs
|
|
14
|
-
} from "./chunk-VQ27YUHH.js";
|
|
15
|
-
import "./chunk-YOLKSYWK.js";
|
|
16
|
-
import "./chunk-U2Q2XGPZ.js";
|
|
19
|
+
DEFAULT_PORT,
|
|
20
|
+
isServerRunning,
|
|
21
|
+
removePid
|
|
22
|
+
} from "./chunk-OLDHLOMT.js";
|
|
23
|
+
import "./chunk-E3J37GDA.js";
|
|
24
|
+
import "./chunk-7T2ZCZQA.js";
|
|
17
25
|
import {
|
|
18
26
|
__name
|
|
19
27
|
} from "./chunk-7QVYU63E.js";
|
|
20
28
|
|
|
21
|
-
// src/cli/
|
|
22
|
-
|
|
23
|
-
green: /* @__PURE__ */ __name((s) => `\x1B[32m${s}\x1B[0m`, "green"),
|
|
24
|
-
red: /* @__PURE__ */ __name((s) => `\x1B[31m${s}\x1B[0m`, "red"),
|
|
25
|
-
yellow: /* @__PURE__ */ __name((s) => `\x1B[33m${s}\x1B[0m`, "yellow"),
|
|
26
|
-
cyan: /* @__PURE__ */ __name((s) => `\x1B[36m${s}\x1B[0m`, "cyan"),
|
|
27
|
-
dim: /* @__PURE__ */ __name((s) => `\x1B[2m${s}\x1B[0m`, "dim"),
|
|
28
|
-
bold: /* @__PURE__ */ __name((s) => `\x1B[1m${s}\x1B[0m`, "bold"),
|
|
29
|
-
magenta: /* @__PURE__ */ __name((s) => `\x1B[35m${s}\x1B[0m`, "magenta")
|
|
30
|
-
};
|
|
31
|
-
var args = process.argv.slice(2);
|
|
32
|
-
function getFlag(name) {
|
|
33
|
-
const idx = args.indexOf(`--${name}`);
|
|
34
|
-
return idx >= 0 ? args[idx + 1] : void 0;
|
|
35
|
-
}
|
|
36
|
-
__name(getFlag, "getFlag");
|
|
37
|
-
function hasFlag(name) {
|
|
38
|
-
return args.includes(`--${name}`);
|
|
39
|
-
}
|
|
40
|
-
__name(hasFlag, "hasFlag");
|
|
41
|
-
var VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
42
|
-
"repo",
|
|
43
|
-
"depth",
|
|
44
|
-
"collection",
|
|
45
|
-
"pattern",
|
|
46
|
-
"context",
|
|
47
|
-
"name",
|
|
48
|
-
"keep",
|
|
49
|
-
"reranker",
|
|
50
|
-
"only",
|
|
51
|
-
"docs",
|
|
52
|
-
"ignore",
|
|
53
|
-
"meta",
|
|
54
|
-
"k",
|
|
55
|
-
"mode",
|
|
56
|
-
"limit"
|
|
57
|
-
]);
|
|
58
|
-
function stripFlags(argv) {
|
|
59
|
-
const result = [];
|
|
60
|
-
for (let i = 0; i < argv.length; i++) {
|
|
61
|
-
if (argv[i].startsWith("--")) {
|
|
62
|
-
const name = argv[i].slice(2);
|
|
63
|
-
if (VALUE_FLAGS.has(name)) i++;
|
|
64
|
-
continue;
|
|
65
|
-
}
|
|
66
|
-
result.push(argv[i]);
|
|
67
|
-
}
|
|
68
|
-
return result;
|
|
69
|
-
}
|
|
70
|
-
__name(stripFlags, "stripFlags");
|
|
71
|
-
function printResults(results) {
|
|
72
|
-
if (results.length === 0) {
|
|
73
|
-
console.log(c.yellow(" No results found."));
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
for (const r of results) {
|
|
77
|
-
const score = Math.round(r.score * 100);
|
|
78
|
-
if (r.type === "code") {
|
|
79
|
-
const m = r.metadata;
|
|
80
|
-
console.log(
|
|
81
|
-
`${c.green(`[CODE ${score}%]`)} ${c.bold(r.filePath)} \u2014 ${m.name || m.chunkType} ${c.dim(`L${m.startLine}-${m.endLine}`)}`
|
|
82
|
-
);
|
|
83
|
-
console.log(c.dim(r.content.split("\n").slice(0, 5).join("\n")));
|
|
84
|
-
console.log("");
|
|
85
|
-
} else if (r.type === "commit") {
|
|
86
|
-
const m = r.metadata;
|
|
87
|
-
console.log(
|
|
88
|
-
`${c.cyan(`[COMMIT ${score}%]`)} ${c.bold(m.shortHash)} ${r.content} ${c.dim(`(${m.author})`)}`
|
|
89
|
-
);
|
|
90
|
-
if (m.files?.length) console.log(c.dim(` Files: ${m.files.slice(0, 4).join(", ")}`));
|
|
91
|
-
console.log("");
|
|
92
|
-
} else if (r.type === "document") {
|
|
93
|
-
const ctx = r.context ? ` \u2014 ${c.dim(r.context)}` : "";
|
|
94
|
-
console.log(
|
|
95
|
-
`${c.magenta(`[DOC ${score}%]`)} ${c.bold(r.filePath)} [${r.metadata.collection}]${ctx}`
|
|
96
|
-
);
|
|
97
|
-
console.log(c.dim(r.content.split("\n").slice(0, 4).join("\n")));
|
|
98
|
-
console.log("");
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
__name(printResults, "printResults");
|
|
103
|
-
|
|
104
|
-
// src/cli/commands/index-cmd.ts
|
|
29
|
+
// src/cli/commands/index.ts
|
|
30
|
+
import * as fs2 from "fs";
|
|
105
31
|
import * as path2 from "path";
|
|
106
32
|
|
|
107
|
-
// src/cli/
|
|
108
|
-
import * as path from "path";
|
|
33
|
+
// src/cli/commands/scan.ts
|
|
109
34
|
import * as fs from "fs";
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
35
|
+
import * as path from "path";
|
|
36
|
+
import { execSync } from "child_process";
|
|
37
|
+
function scanRepo(repoPath) {
|
|
38
|
+
const resolved = path.resolve(repoPath);
|
|
39
|
+
const gitSubdirs = scanGitSubdirs(resolved);
|
|
40
|
+
return {
|
|
41
|
+
repoPath: resolved,
|
|
42
|
+
modules: scanModules(resolved, gitSubdirs),
|
|
43
|
+
config: scanConfig(resolved),
|
|
44
|
+
db: scanDb(resolved),
|
|
45
|
+
gitSubdirs
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
__name(scanRepo, "scanRepo");
|
|
49
|
+
function scanModules(repoPath, gitSubdirs) {
|
|
50
|
+
return [
|
|
51
|
+
scanCodeModule(repoPath),
|
|
52
|
+
scanGitModule(repoPath, gitSubdirs),
|
|
53
|
+
scanDocsModule(repoPath)
|
|
54
|
+
];
|
|
55
|
+
}
|
|
56
|
+
__name(scanModules, "scanModules");
|
|
57
|
+
function scanCodeModule(repoPath) {
|
|
58
|
+
const byLanguage = /* @__PURE__ */ new Map();
|
|
59
|
+
let total = 0;
|
|
60
|
+
function walk(dir) {
|
|
61
|
+
let entries;
|
|
122
62
|
try {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
63
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
64
|
+
} catch {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
for (const entry of entries) {
|
|
68
|
+
const fullPath = path.join(dir, entry.name);
|
|
69
|
+
const isDir = entry.isDirectory() || entry.isSymbolicLink() && (() => {
|
|
70
|
+
try {
|
|
71
|
+
return fs.statSync(fullPath).isDirectory();
|
|
72
|
+
} catch {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
})();
|
|
76
|
+
if (isDir) {
|
|
77
|
+
if (isIgnoredDir(entry.name)) continue;
|
|
78
|
+
walk(fullPath);
|
|
79
|
+
} else if (entry.isFile()) {
|
|
80
|
+
if (isIgnoredFile(entry.name)) continue;
|
|
81
|
+
const ext = path.extname(entry.name).toLowerCase();
|
|
82
|
+
const lang = SUPPORTED_EXTENSIONS[ext];
|
|
83
|
+
if (!lang) continue;
|
|
84
|
+
byLanguage.set(lang, (byLanguage.get(lang) ?? 0) + 1);
|
|
85
|
+
total++;
|
|
129
86
|
}
|
|
130
|
-
return _configCache;
|
|
131
|
-
} catch (err) {
|
|
132
|
-
console.error(c.red(`Error loading .brainbank/${name}: ${err.message}`));
|
|
133
|
-
process.exit(1);
|
|
134
87
|
}
|
|
135
88
|
}
|
|
136
|
-
|
|
137
|
-
|
|
89
|
+
__name(walk, "walk");
|
|
90
|
+
walk(repoPath);
|
|
91
|
+
if (total === 0) {
|
|
92
|
+
return { name: "code", available: false, summary: "no supported source files found", icon: "\u{1F4C1}", checked: false, disabled: "nothing to index" };
|
|
93
|
+
}
|
|
94
|
+
const langCount = byLanguage.size;
|
|
95
|
+
const sorted = [...byLanguage.entries()].sort((a, b) => b[1] - a[1]);
|
|
96
|
+
const maxShow = 7;
|
|
97
|
+
const shown = sorted.slice(0, maxShow);
|
|
98
|
+
const remaining = sorted.length - maxShow;
|
|
99
|
+
const details = [];
|
|
100
|
+
for (let i = 0; i < shown.length; i++) {
|
|
101
|
+
const [lang, count] = shown[i];
|
|
102
|
+
const isLast = i === shown.length - 1 && remaining <= 0;
|
|
103
|
+
const prefix = isLast ? "\u2514\u2500\u2500" : "\u251C\u2500\u2500";
|
|
104
|
+
details.push(`${prefix} ${lang.padEnd(14)} ${count} files`);
|
|
105
|
+
}
|
|
106
|
+
if (remaining > 0) {
|
|
107
|
+
details.push(`\u2514\u2500\u2500 ...and ${remaining} more`);
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
name: "code",
|
|
111
|
+
available: true,
|
|
112
|
+
summary: `${total} files (${langCount} language${langCount > 1 ? "s" : ""})`,
|
|
113
|
+
icon: "\u{1F4C1}",
|
|
114
|
+
checked: true,
|
|
115
|
+
details
|
|
116
|
+
};
|
|
138
117
|
}
|
|
139
|
-
__name(
|
|
140
|
-
|
|
141
|
-
|
|
118
|
+
__name(scanCodeModule, "scanCodeModule");
|
|
119
|
+
function scanGitModule(repoPath, gitSubdirs) {
|
|
120
|
+
const stats = scanGitStats(repoPath, gitSubdirs);
|
|
121
|
+
if (!stats) {
|
|
122
|
+
return { name: "git", available: false, summary: "no .git directory found", icon: "\u{1F4DC}", checked: false, disabled: "not a git repo" };
|
|
123
|
+
}
|
|
124
|
+
const details = [];
|
|
125
|
+
if (stats.lastMessage) {
|
|
126
|
+
details.push(`Last: ${stats.lastMessage} (${stats.lastDate})`);
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
name: "git",
|
|
130
|
+
available: true,
|
|
131
|
+
summary: `${stats.commitCount.toLocaleString()} commits`,
|
|
132
|
+
icon: "\u{1F4DC}",
|
|
133
|
+
checked: true,
|
|
134
|
+
details
|
|
135
|
+
};
|
|
142
136
|
}
|
|
143
|
-
__name(
|
|
144
|
-
|
|
145
|
-
const
|
|
146
|
-
|
|
137
|
+
__name(scanGitModule, "scanGitModule");
|
|
138
|
+
function scanDocsModule(repoPath) {
|
|
139
|
+
const collections = scanDocsCollections(repoPath);
|
|
140
|
+
if (collections.length === 0) {
|
|
141
|
+
return { name: "docs", available: false, summary: "no documents found", icon: "\u{1F4C4}", checked: false, disabled: "no .md/.mdx files" };
|
|
142
|
+
}
|
|
143
|
+
const totalFiles = collections.reduce((s, d) => s + d.fileCount, 0);
|
|
144
|
+
const details = collections.map((d, i) => {
|
|
145
|
+
const isLast = i === collections.length - 1;
|
|
146
|
+
const prefix = isLast ? "\u2514\u2500\u2500" : "\u251C\u2500\u2500";
|
|
147
|
+
return `${prefix} ${d.name.padEnd(10)} \u2192 ${d.path} (${d.fileCount} files)`;
|
|
148
|
+
});
|
|
149
|
+
return {
|
|
150
|
+
name: "docs",
|
|
151
|
+
available: true,
|
|
152
|
+
summary: `${collections.length} collection${collections.length > 1 ? "s" : ""} (${totalFiles} files)`,
|
|
153
|
+
icon: "\u{1F4C4}",
|
|
154
|
+
checked: true,
|
|
155
|
+
details
|
|
156
|
+
};
|
|
147
157
|
}
|
|
148
|
-
__name(
|
|
149
|
-
|
|
150
|
-
if (
|
|
151
|
-
|
|
152
|
-
const indexersDir = path.resolve(repoPath, ".brainbank", "indexers");
|
|
153
|
-
if (!fs.existsSync(indexersDir)) {
|
|
154
|
-
_folderPluginsCache = [];
|
|
155
|
-
return [];
|
|
158
|
+
__name(scanDocsModule, "scanDocsModule");
|
|
159
|
+
function scanGitStats(repoPath, gitSubdirs) {
|
|
160
|
+
if (fs.existsSync(path.join(repoPath, ".git"))) {
|
|
161
|
+
return gitStats(repoPath);
|
|
156
162
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
163
|
+
if (gitSubdirs.length === 0) return null;
|
|
164
|
+
let totalCommits = 0;
|
|
165
|
+
let latestMessage = "";
|
|
166
|
+
let latestDate = "";
|
|
167
|
+
for (const sub of gitSubdirs) {
|
|
168
|
+
const stats = gitStats(path.join(repoPath, sub.name));
|
|
169
|
+
if (stats) {
|
|
170
|
+
totalCommits += stats.commitCount;
|
|
171
|
+
if (!latestMessage) {
|
|
172
|
+
latestMessage = stats.lastMessage;
|
|
173
|
+
latestDate = stats.lastDate;
|
|
168
174
|
}
|
|
169
|
-
} catch (err) {
|
|
170
|
-
console.error(c.red(`Error loading indexer ${file}: ${err.message}`));
|
|
171
175
|
}
|
|
172
176
|
}
|
|
173
|
-
|
|
174
|
-
return indexers;
|
|
177
|
+
return totalCommits > 0 ? { commitCount: totalCommits, lastMessage: latestMessage, lastDate: latestDate } : null;
|
|
175
178
|
}
|
|
176
|
-
__name(
|
|
177
|
-
function
|
|
179
|
+
__name(scanGitStats, "scanGitStats");
|
|
180
|
+
function gitStats(dir) {
|
|
178
181
|
try {
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
182
|
+
const count = parseInt(execSync("git rev-list --count HEAD", { cwd: dir, encoding: "utf-8" }).trim(), 10);
|
|
183
|
+
const log = execSync('git log -1 --format="%s|%ar"', { cwd: dir, encoding: "utf-8" }).trim();
|
|
184
|
+
const [lastMessage, lastDate] = log.split("|");
|
|
185
|
+
return { commitCount: count, lastMessage: lastMessage ?? "", lastDate: lastDate ?? "" };
|
|
183
186
|
} catch {
|
|
184
|
-
return
|
|
187
|
+
return null;
|
|
185
188
|
}
|
|
186
189
|
}
|
|
187
|
-
__name(
|
|
188
|
-
|
|
189
|
-
const
|
|
190
|
-
const
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
}
|
|
204
|
-
__name(createBrain, "createBrain");
|
|
205
|
-
async function setupProviders(brainOpts, config) {
|
|
206
|
-
const rerankerFlag = getFlag("reranker") ?? config?.reranker;
|
|
207
|
-
if (rerankerFlag === "qwen3") {
|
|
208
|
-
const { Qwen3Reranker } = await import("./qwen3-reranker-3MHEENT5.js");
|
|
209
|
-
brainOpts.reranker = new Qwen3Reranker();
|
|
210
|
-
}
|
|
211
|
-
const embFlag = getFlag("embedding") ?? config?.embedding;
|
|
212
|
-
if (embFlag) {
|
|
213
|
-
const provider = await resolveEmbeddingKey(embFlag);
|
|
214
|
-
brainOpts.embeddingProvider = provider;
|
|
215
|
-
brainOpts.embeddingDims = provider.dims;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
__name(setupProviders, "setupProviders");
|
|
219
|
-
async function registerBuiltins(brain, rp, builtins, config) {
|
|
220
|
-
const resolvedRp = path.resolve(rp);
|
|
221
|
-
const hasRootGit = fs.existsSync(path.join(resolvedRp, ".git"));
|
|
222
|
-
const gitSubdirs = !hasRootGit ? detectGitSubdirs(resolvedRp) : [];
|
|
223
|
-
const codeEmb = config?.code?.embedding ? await resolveEmbeddingKey(config.code.embedding) : void 0;
|
|
224
|
-
const gitEmb = config?.git?.embedding ? await resolveEmbeddingKey(config.git.embedding) : void 0;
|
|
225
|
-
const docsEmb = config?.docs?.embedding ? await resolveEmbeddingKey(config.docs.embedding) : void 0;
|
|
226
|
-
if (gitSubdirs.length > 0 && (builtins.includes("code") || builtins.includes("git"))) {
|
|
227
|
-
console.log(c.cyan(` Multi-repo: found ${gitSubdirs.length} git repos: ${gitSubdirs.map((d) => d.name).join(", ")}`));
|
|
228
|
-
for (const sub of gitSubdirs) {
|
|
229
|
-
if (builtins.includes("code")) {
|
|
230
|
-
brain.use(code({
|
|
231
|
-
repoPath: sub.path,
|
|
232
|
-
name: `code:${sub.name}`,
|
|
233
|
-
embeddingProvider: codeEmb,
|
|
234
|
-
maxFileSize: config?.code?.maxFileSize
|
|
235
|
-
}));
|
|
190
|
+
__name(gitStats, "gitStats");
|
|
191
|
+
function scanDocsCollections(repoPath) {
|
|
192
|
+
const results = [];
|
|
193
|
+
const seen = /* @__PURE__ */ new Set();
|
|
194
|
+
const configPath = path.join(repoPath, ".brainbank", "config.json");
|
|
195
|
+
try {
|
|
196
|
+
if (fs.existsSync(configPath)) {
|
|
197
|
+
const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
198
|
+
const docsCfg = config?.docs;
|
|
199
|
+
const collections = docsCfg?.collections;
|
|
200
|
+
if (collections) {
|
|
201
|
+
for (const coll of collections) {
|
|
202
|
+
const absPath = path.resolve(repoPath, coll.path);
|
|
203
|
+
results.push({ name: coll.name, path: coll.path, fileCount: countDocs(absPath) });
|
|
204
|
+
seen.add(absPath);
|
|
205
|
+
}
|
|
236
206
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
207
|
+
}
|
|
208
|
+
} catch {
|
|
209
|
+
}
|
|
210
|
+
const rootDocs = countDocsShallow(repoPath);
|
|
211
|
+
if (rootDocs > 0) {
|
|
212
|
+
results.push({ name: "(root)", path: ".", fileCount: rootDocs });
|
|
213
|
+
}
|
|
214
|
+
try {
|
|
215
|
+
for (const entry of fs.readdirSync(repoPath, { withFileTypes: true })) {
|
|
216
|
+
if (!entry.isDirectory()) continue;
|
|
217
|
+
if (isIgnoredDir(entry.name)) continue;
|
|
218
|
+
if (entry.name.startsWith(".")) continue;
|
|
219
|
+
const dirPath = path.join(repoPath, entry.name);
|
|
220
|
+
if (seen.has(dirPath)) continue;
|
|
221
|
+
const count = countDocs(dirPath);
|
|
222
|
+
if (count > 0) {
|
|
223
|
+
results.push({ name: entry.name, path: `./${entry.name}`, fileCount: count });
|
|
245
224
|
}
|
|
246
225
|
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
} catch {
|
|
226
|
+
} catch {
|
|
227
|
+
}
|
|
228
|
+
return results;
|
|
229
|
+
}
|
|
230
|
+
__name(scanDocsCollections, "scanDocsCollections");
|
|
231
|
+
function countDocs(dir) {
|
|
232
|
+
let count = 0;
|
|
233
|
+
try {
|
|
234
|
+
for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
235
|
+
const ePath = path.join(dir, e.name);
|
|
236
|
+
const isDir = e.isDirectory() || e.isSymbolicLink() && (() => {
|
|
237
|
+
try {
|
|
238
|
+
return fs.statSync(ePath).isDirectory();
|
|
239
|
+
} catch {
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
})();
|
|
243
|
+
if (isDir) {
|
|
244
|
+
if (isIgnoredDir(e.name)) continue;
|
|
245
|
+
count += countDocs(ePath);
|
|
246
|
+
} else if ((e.isFile() || e.isSymbolicLink()) && /\.mdx?$/i.test(e.name)) {
|
|
247
|
+
count++;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
} catch {
|
|
251
|
+
}
|
|
252
|
+
return count;
|
|
253
|
+
}
|
|
254
|
+
__name(countDocs, "countDocs");
|
|
255
|
+
function countDocsShallow(dir) {
|
|
256
|
+
let count = 0;
|
|
257
|
+
try {
|
|
258
|
+
for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
259
|
+
if (e.isFile() && /\.mdx?$/i.test(e.name)) count++;
|
|
282
260
|
}
|
|
261
|
+
} catch {
|
|
262
|
+
}
|
|
263
|
+
return count;
|
|
264
|
+
}
|
|
265
|
+
__name(countDocsShallow, "countDocsShallow");
|
|
266
|
+
function scanConfig(repoPath) {
|
|
267
|
+
const configPath = path.join(repoPath, ".brainbank", "config.json");
|
|
268
|
+
if (!fs.existsSync(configPath)) return { exists: false };
|
|
269
|
+
try {
|
|
270
|
+
const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
271
|
+
const codeCfg = config?.code;
|
|
272
|
+
return {
|
|
273
|
+
exists: true,
|
|
274
|
+
ignore: codeCfg?.ignore,
|
|
275
|
+
plugins: config?.plugins
|
|
276
|
+
};
|
|
277
|
+
} catch {
|
|
278
|
+
return { exists: false };
|
|
283
279
|
}
|
|
284
280
|
}
|
|
285
|
-
__name(
|
|
281
|
+
__name(scanConfig, "scanConfig");
|
|
282
|
+
function scanDb(repoPath) {
|
|
283
|
+
const dbPath = path.join(repoPath, ".brainbank", "data", "brainbank.db");
|
|
284
|
+
if (!fs.existsSync(dbPath)) return { exists: false, sizeMB: 0 };
|
|
285
|
+
try {
|
|
286
|
+
const stat = fs.statSync(dbPath);
|
|
287
|
+
return {
|
|
288
|
+
exists: true,
|
|
289
|
+
sizeMB: Math.round(stat.size / 1024 / 1024 * 10) / 10,
|
|
290
|
+
lastModified: stat.mtime
|
|
291
|
+
};
|
|
292
|
+
} catch {
|
|
293
|
+
return { exists: false, sizeMB: 0 };
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
__name(scanDb, "scanDb");
|
|
297
|
+
function scanGitSubdirs(repoPath) {
|
|
298
|
+
if (fs.existsSync(path.join(repoPath, ".git"))) return [];
|
|
299
|
+
try {
|
|
300
|
+
let subdirs = fs.readdirSync(repoPath, { withFileTypes: true }).filter((e) => {
|
|
301
|
+
if (e.name.startsWith(".")) return false;
|
|
302
|
+
const isDir = e.isDirectory() || e.isSymbolicLink() && (() => {
|
|
303
|
+
try {
|
|
304
|
+
return fs.statSync(path.join(repoPath, e.name)).isDirectory();
|
|
305
|
+
} catch {
|
|
306
|
+
return false;
|
|
307
|
+
}
|
|
308
|
+
})();
|
|
309
|
+
return isDir;
|
|
310
|
+
}).filter((e) => fs.existsSync(path.join(repoPath, e.name, ".git"))).map((e) => ({ name: e.name }));
|
|
311
|
+
const configRepos = readReposFromConfig(repoPath);
|
|
312
|
+
if (configRepos) {
|
|
313
|
+
subdirs = subdirs.filter((s) => configRepos.includes(s.name));
|
|
314
|
+
}
|
|
315
|
+
return subdirs;
|
|
316
|
+
} catch {
|
|
317
|
+
return [];
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
__name(scanGitSubdirs, "scanGitSubdirs");
|
|
321
|
+
function readReposFromConfig(repoPath) {
|
|
322
|
+
const configPath = path.join(repoPath, ".brainbank", "config.json");
|
|
323
|
+
try {
|
|
324
|
+
if (!fs.existsSync(configPath)) return null;
|
|
325
|
+
const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
326
|
+
const repos = config.repos;
|
|
327
|
+
if (Array.isArray(repos) && repos.every((r) => typeof r === "string")) {
|
|
328
|
+
return repos;
|
|
329
|
+
}
|
|
330
|
+
return null;
|
|
331
|
+
} catch {
|
|
332
|
+
return null;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
__name(readReposFromConfig, "readReposFromConfig");
|
|
286
336
|
|
|
287
|
-
// src/cli/commands/index
|
|
337
|
+
// src/cli/commands/index.ts
|
|
288
338
|
async function cmdIndex() {
|
|
289
|
-
const
|
|
339
|
+
const positional = stripFlags(args);
|
|
340
|
+
const repoPath = positional[1] || ".";
|
|
290
341
|
const force = hasFlag("force");
|
|
291
342
|
const depth = parseInt(getFlag("depth") || "500", 10);
|
|
292
343
|
const onlyRaw = getFlag("only");
|
|
293
344
|
const docsPath = getFlag("docs");
|
|
294
|
-
const
|
|
295
|
-
|
|
345
|
+
const skipPrompt = hasFlag("yes") || hasFlag("y");
|
|
346
|
+
const scan = scanRepo(repoPath);
|
|
347
|
+
printScanTree(scan, depth);
|
|
348
|
+
let modules;
|
|
349
|
+
if (onlyRaw) {
|
|
350
|
+
modules = onlyRaw.split(",").map((s) => s.trim());
|
|
351
|
+
} else if (scan.config.plugins && scan.config.plugins.length > 0) {
|
|
352
|
+
modules = scan.config.plugins;
|
|
353
|
+
console.log(c.dim(`
|
|
354
|
+
Using config: plugins = [${modules.join(", ")}]
|
|
355
|
+
`));
|
|
356
|
+
} else if (skipPrompt) {
|
|
357
|
+
modules = buildDefaultModules(scan);
|
|
358
|
+
} else {
|
|
359
|
+
modules = await promptModules(scan);
|
|
360
|
+
if (modules.length === 0) {
|
|
361
|
+
console.log(c.dim("\n Nothing selected. Exiting.\n"));
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
console.clear();
|
|
365
|
+
console.log(c.bold("\n\u2501\u2501\u2501 BrainBank \u2501\u2501\u2501\n"));
|
|
366
|
+
console.log(" Selected modules:");
|
|
367
|
+
for (const m of modules) {
|
|
368
|
+
console.log(` ${c.green("\u2713")} ${m}`);
|
|
369
|
+
}
|
|
370
|
+
console.log("");
|
|
371
|
+
}
|
|
372
|
+
if (docsPath && !modules.includes("docs")) {
|
|
296
373
|
modules.push("docs");
|
|
297
374
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
console.log(c.
|
|
302
|
-
|
|
303
|
-
if (docsPath) console.log(c.dim(` Docs path: ${docsPath}`));
|
|
375
|
+
if (!scan.config.exists && !skipPrompt) {
|
|
376
|
+
await saveConfig(scan.repoPath, modules);
|
|
377
|
+
}
|
|
378
|
+
console.log(c.bold(`
|
|
379
|
+
\u2501\u2501\u2501 Indexing: ${modules.join(", ")} \u2501\u2501\u2501`));
|
|
304
380
|
const brain = await createBrain(repoPath);
|
|
305
|
-
|
|
306
|
-
await
|
|
381
|
+
await brain.initialize();
|
|
382
|
+
const config = await getConfig(repoPath);
|
|
383
|
+
await registerConfigCollections(brain, repoPath, config);
|
|
307
384
|
if (docsPath) {
|
|
308
385
|
const absDocsPath = path2.resolve(docsPath);
|
|
309
386
|
const collName = path2.basename(absDocsPath);
|
|
310
387
|
try {
|
|
311
|
-
|
|
388
|
+
const docsPlugin = findDocsPlugin(brain);
|
|
389
|
+
await docsPlugin?.addCollection({
|
|
312
390
|
name: collName,
|
|
313
391
|
path: absDocsPath,
|
|
314
392
|
pattern: "**/*.md",
|
|
@@ -322,33 +400,163 @@ async function cmdIndex() {
|
|
|
322
400
|
const result = await brain.index({
|
|
323
401
|
modules,
|
|
324
402
|
forceReindex: force,
|
|
325
|
-
|
|
403
|
+
pluginOptions: { depth },
|
|
326
404
|
onProgress: /* @__PURE__ */ __name((stage, msg) => {
|
|
327
405
|
process.stdout.write(`\r ${c.cyan(stage.toUpperCase())} ${msg} `);
|
|
328
406
|
}, "onProgress")
|
|
329
407
|
});
|
|
330
408
|
console.log("\n");
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
409
|
+
for (const [name, value] of Object.entries(result)) {
|
|
410
|
+
if (!value) continue;
|
|
411
|
+
const v = value;
|
|
412
|
+
if (typeof v.indexed === "number") {
|
|
413
|
+
const parts = [`${v.indexed} indexed`, `${v.skipped ?? 0} skipped`];
|
|
414
|
+
if (typeof v.chunks === "number") parts.push(`${v.chunks} chunks`);
|
|
415
|
+
if (typeof v.removed === "number" && v.removed > 0) parts.push(`${v.removed} removed`);
|
|
416
|
+
console.log(` ${c.green(name)}: ${parts.join(", ")}`);
|
|
417
|
+
} else {
|
|
418
|
+
console.log(` ${c.green(name)}: done`);
|
|
340
419
|
}
|
|
341
420
|
}
|
|
342
421
|
const stats = brain.stats();
|
|
343
422
|
console.log(`
|
|
344
423
|
${c.bold("Totals")}:`);
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
424
|
+
for (const [name, s] of Object.entries(stats)) {
|
|
425
|
+
if (!s || typeof s !== "object") continue;
|
|
426
|
+
const entries = Object.entries(s).map(([k, v]) => `${k}: ${v}`).join(", ");
|
|
427
|
+
console.log(` ${name}: ${entries}`);
|
|
428
|
+
}
|
|
349
429
|
brain.close();
|
|
350
430
|
}
|
|
351
431
|
__name(cmdIndex, "cmdIndex");
|
|
432
|
+
function printScanTree(scan, depth) {
|
|
433
|
+
console.clear();
|
|
434
|
+
console.log(c.bold("\n\u2501\u2501\u2501 BrainBank Scan \u2501\u2501\u2501"));
|
|
435
|
+
console.log(c.dim(` Repo: ${scan.repoPath}`));
|
|
436
|
+
if (scan.gitSubdirs.length > 0) {
|
|
437
|
+
console.log(c.cyan(`
|
|
438
|
+
\u{1F500} Multi-repo \u2014 ${scan.gitSubdirs.length} git repos detected`));
|
|
439
|
+
for (const sub of scan.gitSubdirs) {
|
|
440
|
+
console.log(c.dim(` \u2514\u2500\u2500 ${sub.name}`));
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
for (const mod of scan.modules) {
|
|
444
|
+
console.log("");
|
|
445
|
+
if (mod.available) {
|
|
446
|
+
const extra = mod.name === "git" ? ` (depth: ${depth})` : "";
|
|
447
|
+
console.log(` ${mod.icon} ${c.bold(capitalizeFirst(mod.name))} \u2014 ${mod.summary}${extra}`);
|
|
448
|
+
if (mod.details) {
|
|
449
|
+
for (const d of mod.details) {
|
|
450
|
+
console.log(c.dim(` ${d}`));
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
} else {
|
|
454
|
+
console.log(` ${mod.icon} ${c.dim(`${capitalizeFirst(mod.name)} \u2014 ${mod.summary}`)}`);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
if (scan.config.ignore?.length) {
|
|
458
|
+
console.log(c.dim(` Ignore: ${scan.config.ignore.join(", ")}`));
|
|
459
|
+
}
|
|
460
|
+
console.log("");
|
|
461
|
+
if (scan.config.exists) {
|
|
462
|
+
console.log(` \u2699\uFE0F ${c.dim("Config:")} .brainbank/config.json ${c.green("\u2713")}`);
|
|
463
|
+
}
|
|
464
|
+
if (scan.db?.exists) {
|
|
465
|
+
const ago = scan.db.lastModified ? timeSince(scan.db.lastModified) : "";
|
|
466
|
+
console.log(` \u{1F4BE} ${c.dim("DB:")} ${scan.db.sizeMB} MB${ago ? `, last indexed ${ago}` : ""}`);
|
|
467
|
+
} else {
|
|
468
|
+
console.log(` \u{1F4BE} ${c.dim("DB: new (first index)")}`);
|
|
469
|
+
}
|
|
470
|
+
console.log("");
|
|
471
|
+
}
|
|
472
|
+
__name(printScanTree, "printScanTree");
|
|
473
|
+
function buildDefaultModules(scan) {
|
|
474
|
+
return scan.modules.filter((m) => m.available && m.checked).map((m) => m.name);
|
|
475
|
+
}
|
|
476
|
+
__name(buildDefaultModules, "buildDefaultModules");
|
|
477
|
+
async function promptModules(scan) {
|
|
478
|
+
const { checkbox } = await import("@inquirer/prompts");
|
|
479
|
+
console.log(c.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"));
|
|
480
|
+
const choices = scan.modules.map((m) => ({
|
|
481
|
+
name: `${capitalizeFirst(m.name).padEnd(6)} \u2014 ${m.summary}`,
|
|
482
|
+
value: m.name,
|
|
483
|
+
checked: m.checked && m.available,
|
|
484
|
+
disabled: m.available ? void 0 : m.disabled
|
|
485
|
+
}));
|
|
486
|
+
return checkbox({
|
|
487
|
+
message: "Select modules to index:\n",
|
|
488
|
+
choices
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
__name(promptModules, "promptModules");
|
|
492
|
+
function timeSince(date) {
|
|
493
|
+
const seconds = Math.floor((Date.now() - date.getTime()) / 1e3);
|
|
494
|
+
if (seconds < 60) return "just now";
|
|
495
|
+
const minutes = Math.floor(seconds / 60);
|
|
496
|
+
if (minutes < 60) return `${minutes}m ago`;
|
|
497
|
+
const hours = Math.floor(minutes / 60);
|
|
498
|
+
if (hours < 24) return `${hours}h ago`;
|
|
499
|
+
const days = Math.floor(hours / 24);
|
|
500
|
+
return `${days}d ago`;
|
|
501
|
+
}
|
|
502
|
+
__name(timeSince, "timeSince");
|
|
503
|
+
function capitalizeFirst(s) {
|
|
504
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
505
|
+
}
|
|
506
|
+
__name(capitalizeFirst, "capitalizeFirst");
|
|
507
|
+
async function saveConfig(repoPath, modules) {
|
|
508
|
+
const { select } = await import("@inquirer/prompts");
|
|
509
|
+
const envEmbedding = process.env.BRAINBANK_EMBEDDING;
|
|
510
|
+
const embedding = await select({
|
|
511
|
+
message: "Embedding provider:",
|
|
512
|
+
choices: [
|
|
513
|
+
{
|
|
514
|
+
name: "perplexity-context \u2014 best accuracy (recommended)",
|
|
515
|
+
value: "perplexity-context"
|
|
516
|
+
},
|
|
517
|
+
{
|
|
518
|
+
name: "perplexity \u2014 fast, high quality",
|
|
519
|
+
value: "perplexity"
|
|
520
|
+
},
|
|
521
|
+
{
|
|
522
|
+
name: "openai \u2014 text-embedding-3-small",
|
|
523
|
+
value: "openai"
|
|
524
|
+
},
|
|
525
|
+
{
|
|
526
|
+
name: "local \u2014 offline, no API key needed",
|
|
527
|
+
value: "local"
|
|
528
|
+
}
|
|
529
|
+
],
|
|
530
|
+
default: envEmbedding ?? "perplexity-context"
|
|
531
|
+
});
|
|
532
|
+
const pruner = await select({
|
|
533
|
+
message: "Noise pruner:",
|
|
534
|
+
choices: [
|
|
535
|
+
{
|
|
536
|
+
name: "none \u2014 no pruning (default)",
|
|
537
|
+
value: "none"
|
|
538
|
+
},
|
|
539
|
+
{
|
|
540
|
+
name: "haiku \u2014 AI-powered noise filter (Claude Haiku)",
|
|
541
|
+
value: "haiku"
|
|
542
|
+
}
|
|
543
|
+
],
|
|
544
|
+
default: "none"
|
|
545
|
+
});
|
|
546
|
+
const configDir = path2.join(repoPath, ".brainbank");
|
|
547
|
+
const configPath = path2.join(configDir, "config.json");
|
|
548
|
+
const config = {
|
|
549
|
+
plugins: modules,
|
|
550
|
+
embedding
|
|
551
|
+
};
|
|
552
|
+
if (pruner !== "none") {
|
|
553
|
+
config.pruner = pruner;
|
|
554
|
+
}
|
|
555
|
+
fs2.mkdirSync(configDir, { recursive: true });
|
|
556
|
+
fs2.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
557
|
+
console.log(c.green(` \u2713 Saved ${path2.relative(process.cwd(), configPath)}`));
|
|
558
|
+
}
|
|
559
|
+
__name(saveConfig, "saveConfig");
|
|
352
560
|
|
|
353
561
|
// src/cli/commands/collection.ts
|
|
354
562
|
async function cmdCollection() {
|
|
@@ -365,7 +573,12 @@ async function cmdCollection() {
|
|
|
365
573
|
process.exit(1);
|
|
366
574
|
}
|
|
367
575
|
const brain = await createBrain();
|
|
368
|
-
|
|
576
|
+
const docsPlugin = findDocsPlugin(brain);
|
|
577
|
+
if (!docsPlugin) {
|
|
578
|
+
console.log(c.red("Docs plugin not loaded. Install @brainbank/docs."));
|
|
579
|
+
process.exit(1);
|
|
580
|
+
}
|
|
581
|
+
await docsPlugin.addCollection({
|
|
369
582
|
name,
|
|
370
583
|
path: path3,
|
|
371
584
|
pattern,
|
|
@@ -380,7 +593,13 @@ async function cmdCollection() {
|
|
|
380
593
|
if (sub === "list") {
|
|
381
594
|
const brain = await createBrain();
|
|
382
595
|
await brain.initialize();
|
|
383
|
-
const
|
|
596
|
+
const docsPlugin = findDocsPlugin(brain);
|
|
597
|
+
if (!docsPlugin) {
|
|
598
|
+
console.log(c.yellow(" Docs plugin not loaded."));
|
|
599
|
+
brain.close();
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
const collections = docsPlugin.listCollections();
|
|
384
603
|
if (collections.length === 0) {
|
|
385
604
|
console.log(c.yellow(" No collections registered."));
|
|
386
605
|
} else {
|
|
@@ -401,7 +620,12 @@ async function cmdCollection() {
|
|
|
401
620
|
process.exit(1);
|
|
402
621
|
}
|
|
403
622
|
const brain = await createBrain();
|
|
404
|
-
|
|
623
|
+
const docsPlugin = findDocsPlugin(brain);
|
|
624
|
+
if (!docsPlugin) {
|
|
625
|
+
console.log(c.red("Docs plugin not loaded."));
|
|
626
|
+
process.exit(1);
|
|
627
|
+
}
|
|
628
|
+
await docsPlugin.removeCollection(name);
|
|
405
629
|
console.log(c.green(`\u2713 Collection '${name}' removed.`));
|
|
406
630
|
brain.close();
|
|
407
631
|
return;
|
|
@@ -544,10 +768,16 @@ async function cmdDocs() {
|
|
|
544
768
|
opts.onProgress = (col, file, cur, total) => {
|
|
545
769
|
process.stdout.write(`\r ${c.cyan(col)} [${cur}/${total}] ${file} `);
|
|
546
770
|
};
|
|
547
|
-
const
|
|
771
|
+
const docsPlugin = findDocsPlugin(brain);
|
|
772
|
+
if (!docsPlugin) {
|
|
773
|
+
console.log(c.red(" Docs plugin not loaded. Install @brainbank/docs."));
|
|
774
|
+
process.exit(1);
|
|
775
|
+
}
|
|
776
|
+
const results = await docsPlugin.indexDocs(opts);
|
|
548
777
|
console.log("\n");
|
|
549
778
|
for (const [name, stat] of Object.entries(results)) {
|
|
550
|
-
|
|
779
|
+
const removedStr = stat.removed > 0 ? `, ${c.red(String(stat.removed) + " removed")}` : "";
|
|
780
|
+
console.log(` ${c.green(name)}: ${stat.indexed} indexed, ${stat.skipped} skipped${removedStr}, ${stat.chunks} chunks`);
|
|
551
781
|
}
|
|
552
782
|
brain.close();
|
|
553
783
|
}
|
|
@@ -564,7 +794,12 @@ async function cmdDocSearch() {
|
|
|
564
794
|
console.log(c.bold(`
|
|
565
795
|
\u2501\u2501\u2501 BrainBank Doc Search: "${query}" \u2501\u2501\u2501
|
|
566
796
|
`));
|
|
567
|
-
const
|
|
797
|
+
const docsPlugin = findDocsPlugin(brain);
|
|
798
|
+
if (!docsPlugin) {
|
|
799
|
+
console.log(c.red("Docs plugin not loaded. Install @brainbank/docs."));
|
|
800
|
+
process.exit(1);
|
|
801
|
+
}
|
|
802
|
+
const results = await docsPlugin.search(query, { collection: collection ?? void 0, k });
|
|
568
803
|
if (results.length === 0) {
|
|
569
804
|
console.log(c.yellow(" No results found."));
|
|
570
805
|
brain.close();
|
|
@@ -573,7 +808,7 @@ async function cmdDocSearch() {
|
|
|
573
808
|
for (const r of results) {
|
|
574
809
|
const score = Math.round(r.score * 100);
|
|
575
810
|
const ctx = r.context ? ` \u2014 ${c.dim(r.context)}` : "";
|
|
576
|
-
console.log(`${c.magenta(`[DOC ${score}%]`)} ${c.bold(r.filePath)} [${r.metadata.collection}]${ctx}`);
|
|
811
|
+
console.log(`${c.magenta(`[DOC ${score}%]`)} ${c.bold(r.filePath)} [${r.type === "document" ? r.metadata.collection ?? "" : ""}]${ctx}`);
|
|
577
812
|
const preview = r.content.split("\n").slice(0, 4).join("\n");
|
|
578
813
|
console.log(c.dim(preview));
|
|
579
814
|
console.log("");
|
|
@@ -583,56 +818,244 @@ async function cmdDocSearch() {
|
|
|
583
818
|
__name(cmdDocSearch, "cmdDocSearch");
|
|
584
819
|
|
|
585
820
|
// src/cli/commands/search.ts
|
|
821
|
+
function parseSourceFlags() {
|
|
822
|
+
const NON_SOURCE_FLAGS = /* @__PURE__ */ new Set([
|
|
823
|
+
"repo",
|
|
824
|
+
"depth",
|
|
825
|
+
"collection",
|
|
826
|
+
"pattern",
|
|
827
|
+
"context",
|
|
828
|
+
"name",
|
|
829
|
+
"keep",
|
|
830
|
+
"reranker",
|
|
831
|
+
"pruner",
|
|
832
|
+
"only",
|
|
833
|
+
"docs-path",
|
|
834
|
+
"mode",
|
|
835
|
+
"limit",
|
|
836
|
+
"ignore",
|
|
837
|
+
"meta",
|
|
838
|
+
"k",
|
|
839
|
+
"yes",
|
|
840
|
+
"y",
|
|
841
|
+
"force",
|
|
842
|
+
"verbose"
|
|
843
|
+
]);
|
|
844
|
+
const sources = {};
|
|
845
|
+
const positional = [];
|
|
846
|
+
for (let i = 0; i < args.length; i++) {
|
|
847
|
+
if (args[i].startsWith("--")) {
|
|
848
|
+
const name = args[i].slice(2);
|
|
849
|
+
if (name === "yes" || name === "force" || name === "verbose") continue;
|
|
850
|
+
const next = args[i + 1];
|
|
851
|
+
if (next !== void 0 && /^\d+$/.test(next) && !NON_SOURCE_FLAGS.has(name)) {
|
|
852
|
+
sources[name] = parseInt(next, 10);
|
|
853
|
+
i++;
|
|
854
|
+
continue;
|
|
855
|
+
}
|
|
856
|
+
if (NON_SOURCE_FLAGS.has(name) && next !== void 0 && !next.startsWith("--")) {
|
|
857
|
+
i++;
|
|
858
|
+
}
|
|
859
|
+
continue;
|
|
860
|
+
}
|
|
861
|
+
positional.push(args[i]);
|
|
862
|
+
}
|
|
863
|
+
const query = positional.slice(1).join(" ");
|
|
864
|
+
return { sources, query };
|
|
865
|
+
}
|
|
866
|
+
__name(parseSourceFlags, "parseSourceFlags");
|
|
867
|
+
function printFilterInfo(sources) {
|
|
868
|
+
const entries = Object.entries(sources);
|
|
869
|
+
if (entries.length === 0) return;
|
|
870
|
+
const parts = entries.map(([k, v]) => `${k}=${v}`);
|
|
871
|
+
console.log(c.dim(` Sources: ${parts.join(", ")}`));
|
|
872
|
+
}
|
|
873
|
+
__name(printFilterInfo, "printFilterInfo");
|
|
874
|
+
function buildSearchOptions(sources) {
|
|
875
|
+
return Object.keys(sources).length > 0 ? { sources, source: "cli" } : { sources: {}, source: "cli" };
|
|
876
|
+
}
|
|
877
|
+
__name(buildSearchOptions, "buildSearchOptions");
|
|
586
878
|
async function cmdSearch() {
|
|
587
|
-
const query =
|
|
879
|
+
const { sources, query } = parseSourceFlags();
|
|
588
880
|
if (!query) {
|
|
589
|
-
console.log(c.red("Usage: brainbank search <query>"));
|
|
881
|
+
console.log(c.red("Usage: brainbank search <query> [--code <n>] [--git <n>] [--<source> <n>]"));
|
|
590
882
|
process.exit(1);
|
|
591
883
|
}
|
|
592
884
|
const brain = await createBrain();
|
|
593
885
|
console.log(c.bold(`
|
|
594
886
|
\u2501\u2501\u2501 BrainBank Search: "${query}" \u2501\u2501\u2501
|
|
595
887
|
`));
|
|
596
|
-
|
|
888
|
+
printFilterInfo(sources);
|
|
889
|
+
const opts = buildSearchOptions(sources);
|
|
890
|
+
const results = await brain.search(query, opts);
|
|
597
891
|
printResults(results);
|
|
598
892
|
brain.close();
|
|
599
893
|
}
|
|
600
894
|
__name(cmdSearch, "cmdSearch");
|
|
601
895
|
async function cmdHybridSearch() {
|
|
602
|
-
const query =
|
|
896
|
+
const { sources, query } = parseSourceFlags();
|
|
603
897
|
if (!query) {
|
|
604
|
-
console.log(c.red("Usage: brainbank hsearch <query>"));
|
|
898
|
+
console.log(c.red("Usage: brainbank hsearch <query> [--code <n>] [--git <n>] [--docs <n>] [--<source> <n>]"));
|
|
605
899
|
process.exit(1);
|
|
606
900
|
}
|
|
607
901
|
const brain = await createBrain();
|
|
608
902
|
console.log(c.bold(`
|
|
609
903
|
\u2501\u2501\u2501 BrainBank Hybrid Search: "${query}" \u2501\u2501\u2501`));
|
|
610
|
-
console.log(c.dim(` Mode: vector + BM25 \u2192 Reciprocal Rank Fusion
|
|
611
|
-
|
|
612
|
-
|
|
904
|
+
console.log(c.dim(` Mode: vector + BM25 \u2192 Reciprocal Rank Fusion`));
|
|
905
|
+
printFilterInfo(sources);
|
|
906
|
+
console.log("");
|
|
907
|
+
const opts = buildSearchOptions(sources);
|
|
908
|
+
const results = await brain.hybridSearch(query, opts);
|
|
613
909
|
printResults(results);
|
|
614
910
|
brain.close();
|
|
615
911
|
}
|
|
616
912
|
__name(cmdHybridSearch, "cmdHybridSearch");
|
|
617
913
|
async function cmdKeywordSearch() {
|
|
618
|
-
const query =
|
|
914
|
+
const { sources, query } = parseSourceFlags();
|
|
619
915
|
if (!query) {
|
|
620
|
-
console.log(c.red("Usage: brainbank ksearch <query>"));
|
|
916
|
+
console.log(c.red("Usage: brainbank ksearch <query> [--code <n>] [--git <n>] [--<source> <n>]"));
|
|
621
917
|
process.exit(1);
|
|
622
918
|
}
|
|
623
919
|
const brain = await createBrain();
|
|
624
920
|
await brain.initialize();
|
|
625
921
|
console.log(c.bold(`
|
|
626
922
|
\u2501\u2501\u2501 BrainBank Keyword Search: "${query}" \u2501\u2501\u2501`));
|
|
627
|
-
console.log(c.dim(` Mode: BM25 full-text (instant)
|
|
628
|
-
|
|
629
|
-
|
|
923
|
+
console.log(c.dim(` Mode: BM25 full-text (instant)`));
|
|
924
|
+
printFilterInfo(sources);
|
|
925
|
+
console.log("");
|
|
926
|
+
const opts = buildSearchOptions(sources);
|
|
927
|
+
const results = await brain.searchBM25(query, opts);
|
|
630
928
|
printResults(results);
|
|
631
929
|
brain.close();
|
|
632
930
|
}
|
|
633
931
|
__name(cmdKeywordSearch, "cmdKeywordSearch");
|
|
634
932
|
|
|
933
|
+
// src/cli/server-client.ts
|
|
934
|
+
import * as http from "http";
|
|
935
|
+
async function tryServerContext(options) {
|
|
936
|
+
const info = isServerRunning();
|
|
937
|
+
if (!info) return null;
|
|
938
|
+
try {
|
|
939
|
+
const body = JSON.stringify({
|
|
940
|
+
task: options.task,
|
|
941
|
+
repo: options.repo,
|
|
942
|
+
sources: options.sources,
|
|
943
|
+
pathPrefix: options.pathPrefix,
|
|
944
|
+
affectedFiles: options.affectedFiles
|
|
945
|
+
});
|
|
946
|
+
const response = await httpPost(info.port, "/context", body);
|
|
947
|
+
const data = JSON.parse(response);
|
|
948
|
+
if (data.error) return null;
|
|
949
|
+
return data.context ?? null;
|
|
950
|
+
} catch {
|
|
951
|
+
return null;
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
__name(tryServerContext, "tryServerContext");
|
|
955
|
+
async function serverHealth() {
|
|
956
|
+
const info = isServerRunning();
|
|
957
|
+
if (!info) return null;
|
|
958
|
+
try {
|
|
959
|
+
const response = await httpGet(info.port, "/health");
|
|
960
|
+
return JSON.parse(response);
|
|
961
|
+
} catch {
|
|
962
|
+
return null;
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
__name(serverHealth, "serverHealth");
|
|
966
|
+
function httpPost(port, path3, body) {
|
|
967
|
+
return new Promise((resolve3, reject) => {
|
|
968
|
+
const req = http.request({
|
|
969
|
+
hostname: "127.0.0.1",
|
|
970
|
+
port,
|
|
971
|
+
path: path3,
|
|
972
|
+
method: "POST",
|
|
973
|
+
headers: {
|
|
974
|
+
"Content-Type": "application/json",
|
|
975
|
+
"Content-Length": Buffer.byteLength(body)
|
|
976
|
+
},
|
|
977
|
+
timeout: 12e4
|
|
978
|
+
// 2 minutes — context queries can be slow on first load
|
|
979
|
+
}, (res) => {
|
|
980
|
+
const chunks = [];
|
|
981
|
+
res.on("data", (chunk) => chunks.push(chunk));
|
|
982
|
+
res.on("end", () => resolve3(Buffer.concat(chunks).toString("utf8")));
|
|
983
|
+
});
|
|
984
|
+
req.on("error", reject);
|
|
985
|
+
req.on("timeout", () => {
|
|
986
|
+
req.destroy();
|
|
987
|
+
reject(new Error("Request timed out"));
|
|
988
|
+
});
|
|
989
|
+
req.write(body);
|
|
990
|
+
req.end();
|
|
991
|
+
});
|
|
992
|
+
}
|
|
993
|
+
__name(httpPost, "httpPost");
|
|
994
|
+
function httpGet(port, path3) {
|
|
995
|
+
return new Promise((resolve3, reject) => {
|
|
996
|
+
const req = http.request({
|
|
997
|
+
hostname: "127.0.0.1",
|
|
998
|
+
port,
|
|
999
|
+
path: path3,
|
|
1000
|
+
method: "GET",
|
|
1001
|
+
timeout: 5e3
|
|
1002
|
+
}, (res) => {
|
|
1003
|
+
const chunks = [];
|
|
1004
|
+
res.on("data", (chunk) => chunks.push(chunk));
|
|
1005
|
+
res.on("end", () => resolve3(Buffer.concat(chunks).toString("utf8")));
|
|
1006
|
+
});
|
|
1007
|
+
req.on("error", reject);
|
|
1008
|
+
req.on("timeout", () => {
|
|
1009
|
+
req.destroy();
|
|
1010
|
+
reject(new Error("Request timed out"));
|
|
1011
|
+
});
|
|
1012
|
+
req.end();
|
|
1013
|
+
});
|
|
1014
|
+
}
|
|
1015
|
+
__name(httpGet, "httpGet");
|
|
1016
|
+
|
|
635
1017
|
// src/cli/commands/context.ts
|
|
1018
|
+
function parseContextFlags() {
|
|
1019
|
+
const NON_SOURCE = /* @__PURE__ */ new Set([
|
|
1020
|
+
"repo",
|
|
1021
|
+
"depth",
|
|
1022
|
+
"collection",
|
|
1023
|
+
"pattern",
|
|
1024
|
+
"context",
|
|
1025
|
+
"name",
|
|
1026
|
+
"keep",
|
|
1027
|
+
"reranker",
|
|
1028
|
+
"pruner",
|
|
1029
|
+
"only",
|
|
1030
|
+
"docs-path",
|
|
1031
|
+
"mode",
|
|
1032
|
+
"limit",
|
|
1033
|
+
"ignore",
|
|
1034
|
+
"meta",
|
|
1035
|
+
"k",
|
|
1036
|
+
"yes",
|
|
1037
|
+
"y",
|
|
1038
|
+
"force",
|
|
1039
|
+
"verbose",
|
|
1040
|
+
"path"
|
|
1041
|
+
]);
|
|
1042
|
+
const sources = {};
|
|
1043
|
+
for (let i = 0; i < args.length; i++) {
|
|
1044
|
+
if (!args[i].startsWith("--")) continue;
|
|
1045
|
+
const name = args[i].slice(2);
|
|
1046
|
+
if (name.startsWith("no-")) {
|
|
1047
|
+
sources[name.slice(3)] = 0;
|
|
1048
|
+
continue;
|
|
1049
|
+
}
|
|
1050
|
+
const next = args[i + 1];
|
|
1051
|
+
if (next !== void 0 && /^\d+$/.test(next) && !NON_SOURCE.has(name)) {
|
|
1052
|
+
sources[name] = parseInt(next, 10);
|
|
1053
|
+
i++;
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
return sources;
|
|
1057
|
+
}
|
|
1058
|
+
__name(parseContextFlags, "parseContextFlags");
|
|
636
1059
|
async function cmdContext() {
|
|
637
1060
|
const pos = stripFlags(args);
|
|
638
1061
|
const sub = pos[1];
|
|
@@ -646,7 +1069,12 @@ async function cmdContext() {
|
|
|
646
1069
|
}
|
|
647
1070
|
const brain2 = await createBrain();
|
|
648
1071
|
await brain2.initialize();
|
|
649
|
-
|
|
1072
|
+
const docsPlugin = findDocsPlugin(brain2);
|
|
1073
|
+
if (!docsPlugin) {
|
|
1074
|
+
console.log(c.red("Docs plugin not loaded."));
|
|
1075
|
+
process.exit(1);
|
|
1076
|
+
}
|
|
1077
|
+
docsPlugin.addContext(collection, path3, desc);
|
|
650
1078
|
console.log(c.green(`\u2713 Context added: ${collection}:${path3} \u2192 "${desc}"`));
|
|
651
1079
|
brain2.close();
|
|
652
1080
|
return;
|
|
@@ -654,7 +1082,13 @@ async function cmdContext() {
|
|
|
654
1082
|
if (sub === "list") {
|
|
655
1083
|
const brain2 = await createBrain();
|
|
656
1084
|
await brain2.initialize();
|
|
657
|
-
const
|
|
1085
|
+
const docsPlugin = findDocsPlugin(brain2);
|
|
1086
|
+
if (!docsPlugin) {
|
|
1087
|
+
console.log(c.yellow(" Docs plugin not loaded."));
|
|
1088
|
+
brain2.close();
|
|
1089
|
+
return;
|
|
1090
|
+
}
|
|
1091
|
+
const contexts = docsPlugin.listContexts();
|
|
658
1092
|
if (contexts.length === 0) {
|
|
659
1093
|
console.log(c.yellow(" No contexts configured."));
|
|
660
1094
|
} else {
|
|
@@ -673,14 +1107,126 @@ async function cmdContext() {
|
|
|
673
1107
|
console.log(c.dim(" brainbank context list"));
|
|
674
1108
|
process.exit(1);
|
|
675
1109
|
}
|
|
1110
|
+
const sources = parseContextFlags();
|
|
1111
|
+
const pathPrefix = getFlag("path");
|
|
1112
|
+
const repo = getFlag("repo");
|
|
1113
|
+
const fields = parseFieldFlags();
|
|
1114
|
+
const serverResult = await tryServerContext({
|
|
1115
|
+
task,
|
|
1116
|
+
repo: repo ?? process.cwd(),
|
|
1117
|
+
sources: Object.keys(sources).length > 0 ? sources : void 0,
|
|
1118
|
+
pathPrefix
|
|
1119
|
+
});
|
|
1120
|
+
if (serverResult !== null) {
|
|
1121
|
+
console.log(serverResult);
|
|
1122
|
+
return;
|
|
1123
|
+
}
|
|
676
1124
|
const brain = await createBrain();
|
|
677
|
-
const context = await brain.getContext(task
|
|
1125
|
+
const context = await brain.getContext(task, {
|
|
1126
|
+
sources: Object.keys(sources).length > 0 ? sources : void 0,
|
|
1127
|
+
pathPrefix,
|
|
1128
|
+
source: "cli",
|
|
1129
|
+
fields: Object.keys(fields).length > 0 ? fields : void 0
|
|
1130
|
+
});
|
|
678
1131
|
console.log(context);
|
|
679
1132
|
brain.close();
|
|
680
1133
|
}
|
|
681
1134
|
__name(cmdContext, "cmdContext");
|
|
1135
|
+
function parseFieldFlags() {
|
|
1136
|
+
const FIELD_BOOLEANS = /* @__PURE__ */ new Set(["lines", "symbols", "compact", "expander"]);
|
|
1137
|
+
const FIELD_NEGATABLE = /* @__PURE__ */ new Set(["callTree", "imports"]);
|
|
1138
|
+
const fields = {};
|
|
1139
|
+
for (let i = 0; i < args.length; i++) {
|
|
1140
|
+
if (!args[i].startsWith("--")) continue;
|
|
1141
|
+
const raw = args[i].slice(2);
|
|
1142
|
+
if (raw.startsWith("no-")) {
|
|
1143
|
+
const name = raw.slice(3);
|
|
1144
|
+
if (FIELD_NEGATABLE.has(name)) {
|
|
1145
|
+
fields[name] = false;
|
|
1146
|
+
}
|
|
1147
|
+
continue;
|
|
1148
|
+
}
|
|
1149
|
+
const dotIdx = raw.indexOf(".");
|
|
1150
|
+
if (dotIdx > 0) {
|
|
1151
|
+
const fieldName = raw.slice(0, dotIdx);
|
|
1152
|
+
const rest = raw.slice(dotIdx + 1);
|
|
1153
|
+
const eqIdx = rest.indexOf("=");
|
|
1154
|
+
if (eqIdx > 0) {
|
|
1155
|
+
const key = rest.slice(0, eqIdx);
|
|
1156
|
+
const val = parseInt(rest.slice(eqIdx + 1), 10);
|
|
1157
|
+
if (!isNaN(val)) {
|
|
1158
|
+
fields[fieldName] = { [key]: val };
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
continue;
|
|
1162
|
+
}
|
|
1163
|
+
if (FIELD_BOOLEANS.has(raw)) {
|
|
1164
|
+
fields[raw] = true;
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
return fields;
|
|
1168
|
+
}
|
|
1169
|
+
__name(parseFieldFlags, "parseFieldFlags");
|
|
1170
|
+
|
|
1171
|
+
// src/cli/commands/files.ts
|
|
1172
|
+
async function cmdFiles() {
|
|
1173
|
+
const patterns = [];
|
|
1174
|
+
const showLines = args.includes("--lines");
|
|
1175
|
+
for (let i = 1; i < args.length; i++) {
|
|
1176
|
+
if (args[i].startsWith("--")) {
|
|
1177
|
+
if (args[i] === "--repo") {
|
|
1178
|
+
i++;
|
|
1179
|
+
}
|
|
1180
|
+
continue;
|
|
1181
|
+
}
|
|
1182
|
+
patterns.push(args[i]);
|
|
1183
|
+
}
|
|
1184
|
+
if (patterns.length === 0) {
|
|
1185
|
+
console.log(c.red("Usage: brainbank files <path|glob> [...paths] [--lines]"));
|
|
1186
|
+
console.log(c.dim(" Exact: brainbank files src/auth/login.ts"));
|
|
1187
|
+
console.log(c.dim(" Directory: brainbank files src/graph/"));
|
|
1188
|
+
console.log(c.dim(' Glob: brainbank files "src/**/*.service.ts"'));
|
|
1189
|
+
console.log(c.dim(" Fuzzy: brainbank files plugin.ts"));
|
|
1190
|
+
console.log(c.dim(" Lines: brainbank files src/plugin.ts --lines"));
|
|
1191
|
+
process.exit(1);
|
|
1192
|
+
}
|
|
1193
|
+
const brain = await createBrain();
|
|
1194
|
+
await brain.initialize();
|
|
1195
|
+
const results = brain.resolveFiles(patterns);
|
|
1196
|
+
if (results.length === 0) {
|
|
1197
|
+
console.log(c.yellow("No matching files found in the index."));
|
|
1198
|
+
console.log(c.dim("Run `brainbank index` first to index your codebase."));
|
|
1199
|
+
brain.close();
|
|
1200
|
+
return;
|
|
1201
|
+
}
|
|
1202
|
+
for (const r of results) {
|
|
1203
|
+
const meta = r.metadata;
|
|
1204
|
+
const startLine = meta.startLine ?? 1;
|
|
1205
|
+
console.log(c.bold(`
|
|
1206
|
+
\u2500\u2500 ${r.filePath} \u2500\u2500
|
|
1207
|
+
`));
|
|
1208
|
+
if (showLines) {
|
|
1209
|
+
const codeLines = r.content.split("\n");
|
|
1210
|
+
const pad = String(startLine + codeLines.length - 1).length;
|
|
1211
|
+
for (let i = 0; i < codeLines.length; i++) {
|
|
1212
|
+
const lineNum = c.dim(`${String(startLine + i).padStart(pad)}|`);
|
|
1213
|
+
console.log(`${lineNum} ${codeLines[i]}`);
|
|
1214
|
+
}
|
|
1215
|
+
} else {
|
|
1216
|
+
console.log(r.content);
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
console.log(c.dim(`
|
|
1220
|
+
${results.length} file(s) resolved.`));
|
|
1221
|
+
brain.close();
|
|
1222
|
+
}
|
|
1223
|
+
__name(cmdFiles, "cmdFiles");
|
|
682
1224
|
|
|
683
|
-
// src/cli/commands/
|
|
1225
|
+
// src/cli/commands/stats.ts
|
|
1226
|
+
function formatStatKey(key) {
|
|
1227
|
+
return key.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/_/g, " ").replace(/\b\w/g, (c2) => c2.toUpperCase()).padEnd(16);
|
|
1228
|
+
}
|
|
1229
|
+
__name(formatStatKey, "formatStatKey");
|
|
684
1230
|
async function cmdStats() {
|
|
685
1231
|
const brain = await createBrain();
|
|
686
1232
|
await brain.initialize();
|
|
@@ -688,27 +1234,12 @@ async function cmdStats() {
|
|
|
688
1234
|
console.log(c.bold("\n\u2501\u2501\u2501 BrainBank Stats \u2501\u2501\u2501\n"));
|
|
689
1235
|
console.log(` ${c.cyan("Plugins")}: ${brain.plugins.join(", ")}
|
|
690
1236
|
`);
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
console.log(`
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
}
|
|
698
|
-
if (s.git) {
|
|
699
|
-
console.log(` ${c.cyan("Git History")}`);
|
|
700
|
-
console.log(` Commits: ${s.git.commits}`);
|
|
701
|
-
console.log(` Files tracked: ${s.git.filesTracked}`);
|
|
702
|
-
console.log(` Co-edit pairs: ${s.git.coEdits}`);
|
|
703
|
-
console.log(` HNSW vectors: ${s.git.hnswSize}`);
|
|
704
|
-
console.log("");
|
|
705
|
-
}
|
|
706
|
-
if (s.documents) {
|
|
707
|
-
console.log(` ${c.cyan("Documents")}`);
|
|
708
|
-
console.log(` Collections: ${s.documents.collections}`);
|
|
709
|
-
console.log(` Documents: ${s.documents.documents}`);
|
|
710
|
-
console.log(` Chunks: ${s.documents.chunks}`);
|
|
711
|
-
console.log(` HNSW vectors: ${s.documents.hnswSize}`);
|
|
1237
|
+
for (const [name, pluginStats] of Object.entries(s)) {
|
|
1238
|
+
if (!pluginStats) continue;
|
|
1239
|
+
console.log(` ${c.cyan(name)}`);
|
|
1240
|
+
for (const [key, value] of Object.entries(pluginStats)) {
|
|
1241
|
+
console.log(` ${formatStatKey(key)}${value}`);
|
|
1242
|
+
}
|
|
712
1243
|
console.log("");
|
|
713
1244
|
}
|
|
714
1245
|
const kvNames = brain.listCollectionNames();
|
|
@@ -723,6 +1254,8 @@ async function cmdStats() {
|
|
|
723
1254
|
brain.close();
|
|
724
1255
|
}
|
|
725
1256
|
__name(cmdStats, "cmdStats");
|
|
1257
|
+
|
|
1258
|
+
// src/cli/commands/reembed.ts
|
|
726
1259
|
async function cmdReembed() {
|
|
727
1260
|
const brain = await createBrain();
|
|
728
1261
|
await brain.initialize();
|
|
@@ -735,29 +1268,37 @@ async function cmdReembed() {
|
|
|
735
1268
|
}, "onProgress")
|
|
736
1269
|
});
|
|
737
1270
|
console.log("\n");
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
1271
|
+
for (const [name, count] of Object.entries(result.counts)) {
|
|
1272
|
+
if (count > 0) {
|
|
1273
|
+
const label = name.charAt(0).toUpperCase() + name.slice(1);
|
|
1274
|
+
console.log(` ${c.green("\u2713")} ${label.padEnd(8)} ${count} vectors`);
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
744
1277
|
console.log(`
|
|
745
1278
|
${c.bold("Total")}: ${result.total} vectors regenerated
|
|
746
1279
|
`);
|
|
747
1280
|
brain.close();
|
|
748
1281
|
}
|
|
749
1282
|
__name(cmdReembed, "cmdReembed");
|
|
1283
|
+
|
|
1284
|
+
// src/cli/commands/watch.ts
|
|
750
1285
|
async function cmdWatch() {
|
|
751
1286
|
const brain = await createBrain();
|
|
752
1287
|
await brain.initialize();
|
|
1288
|
+
const config = await loadConfig(brain.config.repoPath);
|
|
1289
|
+
const codeIgnore = config?.code?.ignore ?? [];
|
|
753
1290
|
console.log(c.bold("\n\u2501\u2501\u2501 BrainBank Watch \u2501\u2501\u2501\n"));
|
|
754
1291
|
console.log(c.dim(` Watching ${brain.config.repoPath} for changes...`));
|
|
1292
|
+
if (codeIgnore.length > 0) {
|
|
1293
|
+
console.log(c.dim(` Ignoring: ${codeIgnore.join(", ")}`));
|
|
1294
|
+
}
|
|
755
1295
|
console.log(c.dim(" Press Ctrl+C to stop.\n"));
|
|
756
1296
|
const watcher = brain.watch({
|
|
757
1297
|
debounceMs: 2e3,
|
|
758
|
-
|
|
1298
|
+
ignore: codeIgnore,
|
|
1299
|
+
onIndex: /* @__PURE__ */ __name((sourceId, pluginName) => {
|
|
759
1300
|
const ts = (/* @__PURE__ */ new Date()).toLocaleTimeString();
|
|
760
|
-
console.log(` ${c.dim(ts)} ${c.green("\u2713")} ${c.cyan(
|
|
1301
|
+
console.log(` ${c.dim(ts)} ${c.green("\u2713")} ${c.cyan(pluginName)}: ${sourceId}`);
|
|
761
1302
|
}, "onIndex"),
|
|
762
1303
|
onError: /* @__PURE__ */ __name((err) => {
|
|
763
1304
|
console.error(` ${c.red("\u2717")} ${err.message}`);
|
|
@@ -773,14 +1314,141 @@ async function cmdWatch() {
|
|
|
773
1314
|
});
|
|
774
1315
|
}
|
|
775
1316
|
__name(cmdWatch, "cmdWatch");
|
|
776
|
-
|
|
777
|
-
|
|
1317
|
+
|
|
1318
|
+
// src/cli/commands/mcp.ts
|
|
1319
|
+
async function cmdMcp() {
|
|
1320
|
+
try {
|
|
1321
|
+
await import("@brainbank/mcp");
|
|
1322
|
+
} catch {
|
|
1323
|
+
console.error(c.red("Error: @brainbank/mcp is not installed."));
|
|
1324
|
+
console.error(c.dim(" Install: npm i @brainbank/mcp"));
|
|
1325
|
+
process.exit(1);
|
|
1326
|
+
}
|
|
778
1327
|
}
|
|
779
|
-
__name(
|
|
1328
|
+
__name(cmdMcp, "cmdMcp");
|
|
1329
|
+
|
|
1330
|
+
// src/cli/commands/daemon.ts
|
|
1331
|
+
async function cmdDaemon() {
|
|
1332
|
+
const pos = stripFlags(args);
|
|
1333
|
+
const sub = pos[1];
|
|
1334
|
+
if (sub === "stop") return stopDaemon();
|
|
1335
|
+
if (sub === "restart") {
|
|
1336
|
+
stopDaemon();
|
|
1337
|
+
return forkDaemon();
|
|
1338
|
+
}
|
|
1339
|
+
if (sub === "start") return forkDaemon();
|
|
1340
|
+
return startForeground();
|
|
1341
|
+
}
|
|
1342
|
+
__name(cmdDaemon, "cmdDaemon");
|
|
1343
|
+
async function startForeground() {
|
|
1344
|
+
const port = parseInt(getFlag("port") ?? String(DEFAULT_PORT), 10);
|
|
1345
|
+
const { HttpServer } = await import("./http-server-GIRELCCL.js");
|
|
1346
|
+
const server = new HttpServer({
|
|
1347
|
+
port,
|
|
1348
|
+
factory: /* @__PURE__ */ __name(async (repoPath) => {
|
|
1349
|
+
const brain = await createBrain(repoPath);
|
|
1350
|
+
await brain.initialize();
|
|
1351
|
+
return brain;
|
|
1352
|
+
}, "factory"),
|
|
1353
|
+
onError: /* @__PURE__ */ __name((repo, err) => {
|
|
1354
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1355
|
+
console.error(c.red(` Pool error [${repo}]: ${msg}`));
|
|
1356
|
+
}, "onError"),
|
|
1357
|
+
onLog: /* @__PURE__ */ __name((msg) => console.log(c.dim(` ${msg}`)), "onLog")
|
|
1358
|
+
});
|
|
1359
|
+
console.log(c.bold("\n\u2501\u2501\u2501 BrainBank HTTP Daemon \u2501\u2501\u2501\n"));
|
|
1360
|
+
await server.start();
|
|
1361
|
+
console.log(c.dim(` Port: ${port}`));
|
|
1362
|
+
console.log(c.dim(" Press Ctrl+C to stop.\n"));
|
|
1363
|
+
const shutdown = /* @__PURE__ */ __name(() => {
|
|
1364
|
+
console.log(c.dim("\n Shutting down..."));
|
|
1365
|
+
server.close();
|
|
1366
|
+
process.exit(0);
|
|
1367
|
+
}, "shutdown");
|
|
1368
|
+
process.on("SIGINT", shutdown);
|
|
1369
|
+
process.on("SIGTERM", shutdown);
|
|
1370
|
+
await new Promise(() => {
|
|
1371
|
+
});
|
|
1372
|
+
}
|
|
1373
|
+
__name(startForeground, "startForeground");
|
|
1374
|
+
async function forkDaemon() {
|
|
1375
|
+
const port = parseInt(getFlag("port") ?? String(DEFAULT_PORT), 10);
|
|
1376
|
+
const { fork } = await import("child_process");
|
|
1377
|
+
const existing = isServerRunning();
|
|
1378
|
+
if (existing) {
|
|
1379
|
+
console.log(c.yellow(` Daemon already running (PID ${existing.pid}, port ${existing.port})`));
|
|
1380
|
+
return;
|
|
1381
|
+
}
|
|
1382
|
+
const child = fork(process.argv[1], ["daemon", "--port", String(port)], {
|
|
1383
|
+
detached: true,
|
|
1384
|
+
stdio: "ignore"
|
|
1385
|
+
});
|
|
1386
|
+
child.unref();
|
|
1387
|
+
console.log(c.green(` \u2713 Daemon started (PID ${child.pid}, port ${port})`));
|
|
1388
|
+
console.log(c.dim(" Stop with: brainbank daemon stop"));
|
|
1389
|
+
}
|
|
1390
|
+
__name(forkDaemon, "forkDaemon");
|
|
1391
|
+
function stopDaemon() {
|
|
1392
|
+
const info = isServerRunning();
|
|
1393
|
+
if (!info) {
|
|
1394
|
+
console.log(c.yellow(" No daemon running."));
|
|
1395
|
+
return;
|
|
1396
|
+
}
|
|
1397
|
+
try {
|
|
1398
|
+
process.kill(info.pid, "SIGTERM");
|
|
1399
|
+
removePid();
|
|
1400
|
+
console.log(c.green(` \u2713 Daemon stopped (PID ${info.pid})`));
|
|
1401
|
+
} catch {
|
|
1402
|
+
removePid();
|
|
1403
|
+
console.log(c.yellow(` PID ${info.pid} not found. Cleaned up stale PID file.`));
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
__name(stopDaemon, "stopDaemon");
|
|
1407
|
+
|
|
1408
|
+
// src/cli/commands/status.ts
|
|
1409
|
+
function formatUptime(seconds) {
|
|
1410
|
+
if (seconds < 60) return `${seconds}s`;
|
|
1411
|
+
const minutes = Math.floor(seconds / 60);
|
|
1412
|
+
if (minutes < 60) return `${minutes}m`;
|
|
1413
|
+
const hours = Math.floor(minutes / 60);
|
|
1414
|
+
const remainMinutes = minutes % 60;
|
|
1415
|
+
return remainMinutes > 0 ? `${hours}h ${remainMinutes}m` : `${hours}h`;
|
|
1416
|
+
}
|
|
1417
|
+
__name(formatUptime, "formatUptime");
|
|
1418
|
+
async function cmdStatus() {
|
|
1419
|
+
const info = isServerRunning();
|
|
1420
|
+
if (!info) {
|
|
1421
|
+
console.log(`
|
|
1422
|
+
${c.dim("HTTP Server:")} ${c.yellow("stopped")}
|
|
1423
|
+
`);
|
|
1424
|
+
console.log(c.dim(" Start with: brainbank daemon"));
|
|
1425
|
+
console.log("");
|
|
1426
|
+
return;
|
|
1427
|
+
}
|
|
1428
|
+
const health = await serverHealth();
|
|
1429
|
+
if (health) {
|
|
1430
|
+
const uptime = formatUptime(health.uptime);
|
|
1431
|
+
console.log(`
|
|
1432
|
+
${c.dim("HTTP Server:")} ${c.green("running")}`);
|
|
1433
|
+
console.log(` ${c.dim("PID:")} ${health.pid}`);
|
|
1434
|
+
console.log(` ${c.dim("Port:")} ${health.port}`);
|
|
1435
|
+
console.log(` ${c.dim("Uptime:")} ${uptime}`);
|
|
1436
|
+
console.log(` ${c.dim("Workspaces:")} ${health.workspaces}`);
|
|
1437
|
+
console.log("");
|
|
1438
|
+
} else {
|
|
1439
|
+
console.log(`
|
|
1440
|
+
${c.dim("HTTP Server:")} ${c.yellow("stale")} (PID ${info.pid} not responding)`);
|
|
1441
|
+
console.log(c.dim(" The PID file may be stale. Restart with: brainbank daemon"));
|
|
1442
|
+
console.log("");
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
__name(cmdStatus, "cmdStatus");
|
|
1446
|
+
|
|
1447
|
+
// src/cli/commands/help.ts
|
|
780
1448
|
function showHelp() {
|
|
781
1449
|
console.log(c.bold("\n\u2501\u2501\u2501 BrainBank \u2014 Semantic Knowledge Bank \u2501\u2501\u2501\n"));
|
|
782
1450
|
console.log(c.bold("Indexing:"));
|
|
783
|
-
console.log(` ${c.cyan("index")} [path]
|
|
1451
|
+
console.log(` ${c.cyan("index")} ${c.dim("(i)")} [path] Index code + git history`);
|
|
784
1452
|
console.log(` ${c.cyan("collection add")} <path> --name Add a document collection`);
|
|
785
1453
|
console.log(` ${c.cyan("collection list")} List collections`);
|
|
786
1454
|
console.log(` ${c.cyan("collection remove")} <name> Remove a collection`);
|
|
@@ -791,11 +1459,13 @@ function showHelp() {
|
|
|
791
1459
|
console.log(` ${c.cyan("hsearch")} <query> Hybrid search (${c.bold("best quality")})`);
|
|
792
1460
|
console.log(` ${c.cyan("ksearch")} <query> Keyword search (BM25, instant)`);
|
|
793
1461
|
console.log(` ${c.cyan("dsearch")} <query> Document search`);
|
|
1462
|
+
console.log(c.dim(" All search commands accept --<source> <n> to filter by source"));
|
|
794
1463
|
console.log("");
|
|
795
1464
|
console.log(c.bold("Context:"));
|
|
796
1465
|
console.log(` ${c.cyan("context")} <task> Get formatted context for a task`);
|
|
797
1466
|
console.log(` ${c.cyan("context add")} <col> <path> <desc> Add context metadata`);
|
|
798
1467
|
console.log(` ${c.cyan("context list")} List all context metadata`);
|
|
1468
|
+
console.log(` ${c.cyan("files")} <path|glob> [...] [--lines] View full indexed files directly`);
|
|
799
1469
|
console.log("");
|
|
800
1470
|
console.log(c.bold("KV Store:"));
|
|
801
1471
|
console.log(` ${c.cyan("kv add")} <coll> <content> Add item to a collection`);
|
|
@@ -808,7 +1478,12 @@ function showHelp() {
|
|
|
808
1478
|
console.log(` ${c.cyan("stats")} Show index statistics`);
|
|
809
1479
|
console.log(` ${c.cyan("reembed")} Re-embed all vectors`);
|
|
810
1480
|
console.log(` ${c.cyan("watch")} Watch files, auto-re-index`);
|
|
811
|
-
console.log(` ${c.cyan("
|
|
1481
|
+
console.log(` ${c.cyan("mcp")} Start MCP server (stdio)`);
|
|
1482
|
+
console.log(` ${c.cyan("daemon")} Start HTTP daemon (foreground)`);
|
|
1483
|
+
console.log(` ${c.cyan("daemon start")} Start HTTP daemon (background)`);
|
|
1484
|
+
console.log(` ${c.cyan("daemon stop")} Stop background daemon`);
|
|
1485
|
+
console.log(` ${c.cyan("daemon restart")} Restart background daemon`);
|
|
1486
|
+
console.log(` ${c.cyan("status")} Show daemon status`);
|
|
812
1487
|
console.log("");
|
|
813
1488
|
console.log(c.bold("Options:"));
|
|
814
1489
|
console.log(` ${c.dim("--repo <path>")} Repository path (default: .)`);
|
|
@@ -817,16 +1492,26 @@ function showHelp() {
|
|
|
817
1492
|
console.log(` ${c.dim("--collection <name>")} Filter by collection`);
|
|
818
1493
|
console.log(` ${c.dim("--pattern <glob>")} Collection glob (default: **/*.md)`);
|
|
819
1494
|
console.log(` ${c.dim("--context <desc>")} Context description`);
|
|
1495
|
+
console.log(` ${c.dim("--<source> <n>")} Source filter: max results from <source> (0 = skip)`);
|
|
1496
|
+
console.log(` ${c.dim("--path <dir>")} Filter context results to files under this path prefix`);
|
|
1497
|
+
console.log(` ${c.dim("--ignore <globs>")} Ignore glob patterns for code indexing (comma-separated)`);
|
|
1498
|
+
console.log(` ${c.dim("--yes / -y")} Skip interactive prompt (auto-select all available)`);
|
|
820
1499
|
console.log(` ${c.dim("--reranker <name>")} Reranker to use (qwen3)`);
|
|
1500
|
+
console.log(` ${c.dim("--port <n>")} HTTP daemon port (default: 8181)`);
|
|
821
1501
|
console.log("");
|
|
822
1502
|
console.log(c.bold("Examples:"));
|
|
823
1503
|
console.log(c.dim(" brainbank index ."));
|
|
1504
|
+
console.log(c.dim(' brainbank index . --ignore "sdk/**,vendor/**"'));
|
|
824
1505
|
console.log(c.dim(' brainbank kv add errors "Fixed null pointer in api.ts"'));
|
|
825
1506
|
console.log(c.dim(' brainbank kv search errors "null pointer"'));
|
|
826
1507
|
console.log(c.dim(" brainbank kv list"));
|
|
827
1508
|
console.log(c.dim(' brainbank hsearch "authentication middleware"'));
|
|
828
|
-
console.log(c.dim(' brainbank
|
|
829
|
-
console.log(c.dim(
|
|
1509
|
+
console.log(c.dim(' brainbank hsearch "auth" --code 0 --git 10 # git only'));
|
|
1510
|
+
console.log(c.dim(' brainbank search "handler" --git 0 # code only'));
|
|
1511
|
+
console.log(c.dim(' brainbank hsearch "api" --docs 10 --code 0 --git 0 # docs only'));
|
|
1512
|
+
console.log(c.dim(' brainbank context "auth flow" | pbcopy # \u2192 clipboard'));
|
|
1513
|
+
console.log(c.dim(" brainbank daemon start # background HTTP"));
|
|
1514
|
+
console.log(c.dim(" brainbank mcp # MCP stdio"));
|
|
830
1515
|
}
|
|
831
1516
|
__name(showHelp, "showHelp");
|
|
832
1517
|
|
|
@@ -834,6 +1519,7 @@ __name(showHelp, "showHelp");
|
|
|
834
1519
|
var command = args[0];
|
|
835
1520
|
async function main() {
|
|
836
1521
|
switch (command) {
|
|
1522
|
+
case "i":
|
|
837
1523
|
case "index":
|
|
838
1524
|
return cmdIndex();
|
|
839
1525
|
case "collection":
|
|
@@ -852,14 +1538,23 @@ async function main() {
|
|
|
852
1538
|
return cmdKeywordSearch();
|
|
853
1539
|
case "context":
|
|
854
1540
|
return cmdContext();
|
|
1541
|
+
case "files":
|
|
1542
|
+
return cmdFiles();
|
|
855
1543
|
case "stats":
|
|
856
1544
|
return cmdStats();
|
|
857
1545
|
case "reembed":
|
|
858
1546
|
return cmdReembed();
|
|
859
1547
|
case "watch":
|
|
860
1548
|
return cmdWatch();
|
|
1549
|
+
case "mcp":
|
|
1550
|
+
return cmdMcp();
|
|
861
1551
|
case "serve":
|
|
862
|
-
return
|
|
1552
|
+
return cmdMcp();
|
|
1553
|
+
// backward compat
|
|
1554
|
+
case "daemon":
|
|
1555
|
+
return cmdDaemon();
|
|
1556
|
+
case "status":
|
|
1557
|
+
return cmdStatus();
|
|
863
1558
|
case "help":
|
|
864
1559
|
case "--help":
|
|
865
1560
|
case "-h":
|