brainbank 0.1.0-beta.1

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.
Files changed (137) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +155 -0
  3. package/assets/architecture.png +0 -0
  4. package/bin/brainbank +18 -0
  5. package/bin/brainbank-mcp +19 -0
  6. package/dist/chunk-3YBCD6DI.js +117 -0
  7. package/dist/chunk-3YBCD6DI.js.map +1 -0
  8. package/dist/chunk-63GBCDS5.js +3249 -0
  9. package/dist/chunk-63GBCDS5.js.map +1 -0
  10. package/dist/chunk-DMFMTOHF.js +123 -0
  11. package/dist/chunk-DMFMTOHF.js.map +1 -0
  12. package/dist/chunk-FQYKWB2Q.js +136 -0
  13. package/dist/chunk-FQYKWB2Q.js.map +1 -0
  14. package/dist/chunk-IMJJ2VEM.js +74 -0
  15. package/dist/chunk-IMJJ2VEM.js.map +1 -0
  16. package/dist/chunk-M744PCJQ.js +43 -0
  17. package/dist/chunk-M744PCJQ.js.map +1 -0
  18. package/dist/chunk-O3J6ZIXK.js +82 -0
  19. package/dist/chunk-O3J6ZIXK.js.map +1 -0
  20. package/dist/chunk-OPH7GZ7U.js +124 -0
  21. package/dist/chunk-OPH7GZ7U.js.map +1 -0
  22. package/dist/chunk-PXEWQMN7.js +89 -0
  23. package/dist/chunk-PXEWQMN7.js.map +1 -0
  24. package/dist/chunk-RDQYDLYZ.js +69 -0
  25. package/dist/chunk-RDQYDLYZ.js.map +1 -0
  26. package/dist/chunk-VIIHPCC4.js +254 -0
  27. package/dist/chunk-VIIHPCC4.js.map +1 -0
  28. package/dist/chunk-WCQVDF3K.js +14 -0
  29. package/dist/chunk-WCQVDF3K.js.map +1 -0
  30. package/dist/cli.d.ts +1 -0
  31. package/dist/cli.js +3076 -0
  32. package/dist/cli.js.map +1 -0
  33. package/dist/haiku-expander-YRSIPGKP.js +8 -0
  34. package/dist/haiku-expander-YRSIPGKP.js.map +1 -0
  35. package/dist/haiku-pruner-SHAXUPY6.js +8 -0
  36. package/dist/haiku-pruner-SHAXUPY6.js.map +1 -0
  37. package/dist/http-server-QUXHLWUM.js +9 -0
  38. package/dist/http-server-QUXHLWUM.js.map +1 -0
  39. package/dist/index.d.ts +2161 -0
  40. package/dist/index.js +357 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/local-embedding-NZQTILGV.js +8 -0
  43. package/dist/local-embedding-NZQTILGV.js.map +1 -0
  44. package/dist/mcp.d.ts +2 -0
  45. package/dist/mcp.js +334 -0
  46. package/dist/mcp.js.map +1 -0
  47. package/dist/openai-embedding-ZP5TSUJG.js +8 -0
  48. package/dist/openai-embedding-ZP5TSUJG.js.map +1 -0
  49. package/dist/perplexity-context-embedding-GI5PHE6X.js +9 -0
  50. package/dist/perplexity-context-embedding-GI5PHE6X.js.map +1 -0
  51. package/dist/perplexity-embedding-KZRYGJRC.js +10 -0
  52. package/dist/perplexity-embedding-KZRYGJRC.js.map +1 -0
  53. package/dist/plugin-IKQ6IRSJ.js +32 -0
  54. package/dist/plugin-IKQ6IRSJ.js.map +1 -0
  55. package/dist/resolve-ASGLBNUC.js +10 -0
  56. package/dist/resolve-ASGLBNUC.js.map +1 -0
  57. package/dist/stats-tui-ZY2NQSEA.js +1904 -0
  58. package/dist/stats-tui-ZY2NQSEA.js.map +1 -0
  59. package/package.json +96 -0
  60. package/src/brainbank.ts +617 -0
  61. package/src/cli/commands/collection.ts +77 -0
  62. package/src/cli/commands/context.ts +179 -0
  63. package/src/cli/commands/daemon.ts +100 -0
  64. package/src/cli/commands/docs.ts +71 -0
  65. package/src/cli/commands/files.ts +69 -0
  66. package/src/cli/commands/help.ts +77 -0
  67. package/src/cli/commands/index.ts +482 -0
  68. package/src/cli/commands/kv.ts +140 -0
  69. package/src/cli/commands/mcp-export.ts +273 -0
  70. package/src/cli/commands/mcp.ts +6 -0
  71. package/src/cli/commands/reembed.ts +30 -0
  72. package/src/cli/commands/scan.ts +336 -0
  73. package/src/cli/commands/search.ts +203 -0
  74. package/src/cli/commands/stats.ts +68 -0
  75. package/src/cli/commands/status.ts +47 -0
  76. package/src/cli/commands/watch.ts +47 -0
  77. package/src/cli/factory/brain-context.ts +43 -0
  78. package/src/cli/factory/builtin-registration.ts +87 -0
  79. package/src/cli/factory/config-loader.ts +77 -0
  80. package/src/cli/factory/index.ts +69 -0
  81. package/src/cli/factory/plugin-loader.ts +325 -0
  82. package/src/cli/index.ts +71 -0
  83. package/src/cli/server-client.ts +178 -0
  84. package/src/cli/tui/index-tui.tsx +667 -0
  85. package/src/cli/tui/stats-data.ts +523 -0
  86. package/src/cli/tui/stats-search.ts +262 -0
  87. package/src/cli/tui/stats-tui.tsx +1465 -0
  88. package/src/cli/tui/tree-scanner.ts +650 -0
  89. package/src/cli/utils.ts +137 -0
  90. package/src/config.ts +49 -0
  91. package/src/constants.ts +21 -0
  92. package/src/db/adapter.ts +112 -0
  93. package/src/db/metadata.ts +130 -0
  94. package/src/db/migrations.ts +66 -0
  95. package/src/db/sqlite-adapter.ts +218 -0
  96. package/src/db/tracker.ts +91 -0
  97. package/src/engine/index-api.ts +81 -0
  98. package/src/engine/reembed.ts +206 -0
  99. package/src/engine/search-api.ts +218 -0
  100. package/src/index.ts +154 -0
  101. package/src/lib/fts.ts +57 -0
  102. package/src/lib/languages.ts +180 -0
  103. package/src/lib/logger.ts +126 -0
  104. package/src/lib/math.ts +87 -0
  105. package/src/lib/provider-key.ts +20 -0
  106. package/src/lib/prune.ts +71 -0
  107. package/src/lib/rrf.ts +133 -0
  108. package/src/lib/write-lock.ts +108 -0
  109. package/src/mcp/mcp-server.ts +195 -0
  110. package/src/mcp/workspace-factory.ts +68 -0
  111. package/src/mcp/workspace-pool.ts +224 -0
  112. package/src/plugin.ts +381 -0
  113. package/src/providers/embeddings/embedding-worker-thread.ts +95 -0
  114. package/src/providers/embeddings/embedding-worker.ts +141 -0
  115. package/src/providers/embeddings/local-embedding.ts +115 -0
  116. package/src/providers/embeddings/openai-embedding.ts +167 -0
  117. package/src/providers/embeddings/perplexity-context-embedding.ts +195 -0
  118. package/src/providers/embeddings/perplexity-embedding.ts +165 -0
  119. package/src/providers/embeddings/resolve.ts +34 -0
  120. package/src/providers/pruners/haiku-expander.ts +166 -0
  121. package/src/providers/pruners/haiku-pruner.ts +112 -0
  122. package/src/providers/vector/hnsw-index.ts +174 -0
  123. package/src/providers/vector/hnsw-loader.ts +129 -0
  124. package/src/search/bm25-boost.ts +69 -0
  125. package/src/search/context-builder.ts +251 -0
  126. package/src/search/keyword/composite-bm25-search.ts +47 -0
  127. package/src/search/types.ts +37 -0
  128. package/src/search/vector/composite-vector-search.ts +61 -0
  129. package/src/search/vector/mmr.ts +64 -0
  130. package/src/services/collection.ts +384 -0
  131. package/src/services/daemon.ts +87 -0
  132. package/src/services/http-server.ts +336 -0
  133. package/src/services/kv-service.ts +64 -0
  134. package/src/services/plugin-registry.ts +77 -0
  135. package/src/services/watch.ts +340 -0
  136. package/src/services/webhook-server.ts +100 -0
  137. package/src/types.ts +493 -0
@@ -0,0 +1,1904 @@
1
+ import {
2
+ createBrain,
3
+ pruneResults
4
+ } from "./chunk-63GBCDS5.js";
5
+ import "./chunk-M744PCJQ.js";
6
+ import {
7
+ isExpandablePlugin
8
+ } from "./chunk-IMJJ2VEM.js";
9
+ import {
10
+ __name
11
+ } from "./chunk-WCQVDF3K.js";
12
+
13
+ // src/cli/tui/stats-tui.tsx
14
+ import { useState, useMemo, useEffect, useRef, useCallback } from "react";
15
+ import { render, Box, Text, useApp, useInput, useStdout } from "ink";
16
+
17
+ // src/cli/tui/stats-data.ts
18
+ import { DatabaseSync } from "node:sqlite";
19
+ import * as fs from "fs";
20
+ import * as path from "path";
21
+ function openDb(dbPath) {
22
+ return new DatabaseSync(dbPath, { readOnly: true });
23
+ }
24
+ __name(openDb, "openDb");
25
+ function tableExists(db, name) {
26
+ const row = db.prepare(
27
+ `SELECT 1 as found FROM sqlite_master WHERE type='table' AND name=?`
28
+ ).get(name);
29
+ return !!row;
30
+ }
31
+ __name(tableExists, "tableExists");
32
+ function countQuery(db, sql, ...params) {
33
+ const row = db.prepare(sql).get(...params);
34
+ return row?.c ?? 0;
35
+ }
36
+ __name(countQuery, "countQuery");
37
+ function fetchOverview(dbPath, repoPath, configPath) {
38
+ const db = openDb(dbPath);
39
+ try {
40
+ const hasChunks = tableExists(db, "code_chunks");
41
+ const files = hasChunks ? countQuery(db, "SELECT COUNT(DISTINCT file_path) as c FROM code_chunks") : 0;
42
+ const chunks = hasChunks ? countQuery(db, "SELECT COUNT(*) as c FROM code_chunks") : 0;
43
+ const symbols = hasChunks ? countQuery(db, "SELECT COUNT(*) as c FROM code_chunks WHERE name IS NOT NULL AND name != ''") : 0;
44
+ const callEdges = tableExists(db, "code_call_edges") ? countQuery(db, "SELECT COUNT(*) as c FROM code_call_edges") : 0;
45
+ const importEdges = tableExists(db, "code_imports") ? countQuery(db, "SELECT COUNT(*) as c FROM code_imports") : 0;
46
+ const hnswSize = tableExists(db, "code_vectors") ? countQuery(db, "SELECT COUNT(*) as c FROM code_vectors") : 0;
47
+ const stat = fs.statSync(dbPath);
48
+ const dbSizeMB = Math.round(stat.size / 1048576 * 10) / 10;
49
+ let embeddingModel = "unknown";
50
+ if (tableExists(db, "embedding_meta")) {
51
+ for (const key of ["provider_key", "provider", "model"]) {
52
+ const row = db.prepare(`SELECT value FROM embedding_meta WHERE key = ?`).get(key);
53
+ if (row) {
54
+ embeddingModel = row.value;
55
+ break;
56
+ }
57
+ }
58
+ }
59
+ let plugins = ["code"];
60
+ let pruner = "none";
61
+ let expander = "none";
62
+ try {
63
+ const raw = fs.readFileSync(configPath, "utf-8");
64
+ const config = JSON.parse(raw);
65
+ if (Array.isArray(config.plugins)) plugins = config.plugins;
66
+ if (typeof config.pruner === "string") pruner = config.pruner;
67
+ if (typeof config.expander === "string") expander = config.expander;
68
+ } catch {
69
+ }
70
+ return { files, chunks, symbols, callEdges, importEdges, hnswSize, dbSizeMB, embeddingModel, repoPath, plugins, pruner, expander };
71
+ } finally {
72
+ db.close();
73
+ }
74
+ }
75
+ __name(fetchOverview, "fetchOverview");
76
+ function fetchLanguageBreakdown(dbPath) {
77
+ const db = openDb(dbPath);
78
+ try {
79
+ if (!tableExists(db, "code_chunks")) return [];
80
+ const rows = db.prepare(`
81
+ SELECT language, COUNT(*) as chunks, COUNT(DISTINCT file_path) as files
82
+ FROM code_chunks
83
+ GROUP BY language
84
+ ORDER BY chunks DESC
85
+ `).all();
86
+ const total = rows.reduce((sum, r) => sum + r.chunks, 0);
87
+ return rows.map((r) => ({
88
+ language: r.language,
89
+ chunks: r.chunks,
90
+ files: r.files,
91
+ percent: total > 0 ? Math.round(r.chunks / total * 1e3) / 10 : 0
92
+ }));
93
+ } finally {
94
+ db.close();
95
+ }
96
+ }
97
+ __name(fetchLanguageBreakdown, "fetchLanguageBreakdown");
98
+ function fetchDirectories(dbPath) {
99
+ const db = openDb(dbPath);
100
+ try {
101
+ if (!tableExists(db, "code_chunks")) return [];
102
+ const rows = db.prepare(`
103
+ SELECT
104
+ CASE
105
+ WHEN INSTR(file_path, '/') > 0 THEN SUBSTR(file_path, 1, INSTR(file_path, '/') - 1)
106
+ ELSE file_path
107
+ END as dir,
108
+ COUNT(DISTINCT file_path) as files,
109
+ COUNT(*) as chunks
110
+ FROM code_chunks
111
+ GROUP BY dir
112
+ ORDER BY chunks DESC
113
+ `).all();
114
+ const total = rows.reduce((sum, r) => sum + r.chunks, 0);
115
+ return rows.map((r) => ({
116
+ dir: r.dir,
117
+ files: r.files,
118
+ chunks: r.chunks,
119
+ percent: total > 0 ? Math.round(r.chunks / total * 1e3) / 10 : 0
120
+ }));
121
+ } finally {
122
+ db.close();
123
+ }
124
+ }
125
+ __name(fetchDirectories, "fetchDirectories");
126
+ function fetchFilesInDir(dbPath, dir) {
127
+ const db = openDb(dbPath);
128
+ try {
129
+ if (!tableExists(db, "code_chunks")) return [];
130
+ const rows = db.prepare(`
131
+ SELECT
132
+ file_path,
133
+ language,
134
+ COUNT(*) as chunks,
135
+ COUNT(CASE WHEN name IS NOT NULL AND name != '' THEN 1 END) as symbols,
136
+ MIN(start_line) as min_line,
137
+ MAX(end_line) as max_line
138
+ FROM code_chunks
139
+ WHERE file_path LIKE ? || '%'
140
+ GROUP BY file_path
141
+ ORDER BY chunks DESC, file_path
142
+ `).all(`${dir}/`);
143
+ return rows.map((r) => ({
144
+ filePath: r.file_path,
145
+ fileName: path.basename(r.file_path),
146
+ language: r.language,
147
+ chunks: r.chunks,
148
+ symbols: r.symbols,
149
+ startLine: r.min_line,
150
+ endLine: r.max_line
151
+ }));
152
+ } finally {
153
+ db.close();
154
+ }
155
+ }
156
+ __name(fetchFilesInDir, "fetchFilesInDir");
157
+ function fetchFileDetail(dbPath, filePath) {
158
+ const db = openDb(dbPath);
159
+ try {
160
+ const meta = db.prepare(`
161
+ SELECT language, COUNT(*) as chunks
162
+ FROM code_chunks WHERE file_path = ?
163
+ `).get(filePath);
164
+ const symbols = db.prepare(`
165
+ SELECT name, chunk_type as kind, start_line as line
166
+ FROM code_chunks
167
+ WHERE file_path = ? AND name IS NOT NULL AND name != ''
168
+ ORDER BY start_line
169
+ `).all(filePath);
170
+ let importsOut = [];
171
+ if (tableExists(db, "code_imports")) {
172
+ importsOut = db.prepare(`
173
+ SELECT imports_path FROM code_imports WHERE file_path = ?
174
+ `).all(filePath).map((r) => r.imports_path);
175
+ }
176
+ let importsIn = [];
177
+ if (tableExists(db, "code_imports")) {
178
+ const base = path.basename(filePath, path.extname(filePath));
179
+ importsIn = db.prepare(`
180
+ SELECT file_path FROM code_imports WHERE imports_path LIKE ?
181
+ `).all(`%${base}%`).map((r) => r.file_path);
182
+ }
183
+ let callEdgesOut = 0;
184
+ let callEdgesIn = 0;
185
+ if (tableExists(db, "code_call_edges")) {
186
+ const chunkIds = db.prepare(
187
+ `SELECT id FROM code_chunks WHERE file_path = ?`
188
+ ).all(filePath).map((r) => r.id);
189
+ if (chunkIds.length > 0) {
190
+ const ph = chunkIds.map(() => "?").join(",");
191
+ callEdgesOut = countQuery(db, `SELECT COUNT(*) as c FROM code_call_edges WHERE caller_chunk_id IN (${ph})`, ...chunkIds);
192
+ callEdgesIn = countQuery(db, `SELECT COUNT(*) as c FROM code_call_edges WHERE callee_chunk_id IN (${ph})`, ...chunkIds);
193
+ }
194
+ }
195
+ return {
196
+ filePath,
197
+ language: meta?.language ?? "unknown",
198
+ chunks: meta?.chunks ?? 0,
199
+ symbols,
200
+ importsOut,
201
+ importsIn,
202
+ callEdgesOut,
203
+ callEdgesIn
204
+ };
205
+ } finally {
206
+ db.close();
207
+ }
208
+ }
209
+ __name(fetchFileDetail, "fetchFileDetail");
210
+ function fetchChunksForFile(dbPath, filePath) {
211
+ const db = openDb(dbPath);
212
+ try {
213
+ if (!tableExists(db, "code_chunks")) return [];
214
+ const rows = db.prepare(`
215
+ SELECT id, chunk_type, name, start_line, end_line, content, language
216
+ FROM code_chunks
217
+ WHERE file_path = ?
218
+ ORDER BY start_line
219
+ `).all(filePath);
220
+ const hasCallEdges = tableExists(db, "code_call_edges");
221
+ return rows.map((r) => {
222
+ let callsOut = [];
223
+ let calledBy = [];
224
+ if (hasCallEdges) {
225
+ callsOut = db.prepare(`
226
+ SELECT DISTINCT symbol_name FROM code_call_edges WHERE caller_chunk_id = ?
227
+ `).all(r.id).map((row) => row.symbol_name);
228
+ calledBy = db.prepare(`
229
+ SELECT DISTINCT symbol_name FROM code_call_edges WHERE callee_chunk_id = ?
230
+ `).all(r.id).map((row) => row.symbol_name);
231
+ }
232
+ return {
233
+ id: r.id,
234
+ chunkType: r.chunk_type,
235
+ name: r.name,
236
+ startLine: r.start_line,
237
+ endLine: r.end_line,
238
+ content: r.content,
239
+ language: r.language,
240
+ callsOut,
241
+ calledBy
242
+ };
243
+ });
244
+ } finally {
245
+ db.close();
246
+ }
247
+ }
248
+ __name(fetchChunksForFile, "fetchChunksForFile");
249
+ function fetchCallTree(dbPath, chunkId, depth = 3) {
250
+ const db = openDb(dbPath);
251
+ try {
252
+ let expand2 = function(id, d, visited2) {
253
+ if (d <= 0 || !tableExists(db, "code_call_edges")) return [];
254
+ const edges = db.prepare(`
255
+ SELECT DISTINCT ce.callee_chunk_id, ce.symbol_name, cc.file_path
256
+ FROM code_call_edges ce
257
+ JOIN code_chunks cc ON cc.id = ce.callee_chunk_id
258
+ WHERE ce.caller_chunk_id = ?
259
+ ORDER BY ce.symbol_name
260
+ `).all(id);
261
+ return edges.filter((e) => !visited2.has(e.callee_chunk_id)).map((e) => {
262
+ visited2.add(e.callee_chunk_id);
263
+ return {
264
+ chunkId: e.callee_chunk_id,
265
+ symbol: e.symbol_name,
266
+ filePath: e.file_path,
267
+ children: expand2(e.callee_chunk_id, d - 1, visited2)
268
+ };
269
+ });
270
+ };
271
+ var expand = expand2;
272
+ __name(expand2, "expand");
273
+ const chunk = db.prepare(`
274
+ SELECT id, name, file_path FROM code_chunks WHERE id = ?
275
+ `).get(chunkId);
276
+ if (!chunk) return { chunkId, symbol: "?", filePath: "?", children: [] };
277
+ const visited = /* @__PURE__ */ new Set([chunkId]);
278
+ return {
279
+ chunkId,
280
+ symbol: chunk.name ?? "anonymous",
281
+ filePath: chunk.file_path,
282
+ children: expand2(chunkId, depth, visited)
283
+ };
284
+ } finally {
285
+ db.close();
286
+ }
287
+ }
288
+ __name(fetchCallTree, "fetchCallTree");
289
+ function searchSymbols(dbPath, query, limit = 10) {
290
+ const db = openDb(dbPath);
291
+ try {
292
+ if (!tableExists(db, "code_chunks")) return [];
293
+ return db.prepare(`
294
+ SELECT id, name, file_path as filePath FROM code_chunks
295
+ WHERE name LIKE ? AND name IS NOT NULL AND name != ''
296
+ ORDER BY name LIMIT ?
297
+ `).all(`%${query}%`, limit);
298
+ } finally {
299
+ db.close();
300
+ }
301
+ }
302
+ __name(searchSymbols, "searchSymbols");
303
+
304
+ // src/cli/tui/stats-search.ts
305
+ var BrainSearchSession = class {
306
+ static {
307
+ __name(this, "BrainSearchSession");
308
+ }
309
+ _brain = null;
310
+ _repoPath;
311
+ _initPromise = null;
312
+ _sources = [];
313
+ constructor(repoPath) {
314
+ this._repoPath = repoPath;
315
+ }
316
+ /** Whether the brain has been initialized. */
317
+ get initialized() {
318
+ return this._brain !== null;
319
+ }
320
+ /** Available source filters (populated after init). */
321
+ get sources() {
322
+ return this._sources;
323
+ }
324
+ /** Pruner name (or null). */
325
+ get prunerName() {
326
+ return this._brain?.config.pruner?.constructor?.name ?? null;
327
+ }
328
+ /** Expander name (or null). */
329
+ get expanderName() {
330
+ return this._brain?.config.expander?.constructor?.name ?? null;
331
+ }
332
+ /**
333
+ * Initialize the BrainBank instance (lazy, idempotent).
334
+ * Creates the brain, loads HNSW + FTS indices.
335
+ */
336
+ async init() {
337
+ if (this._brain) return;
338
+ if (this._initPromise) return this._initPromise;
339
+ this._initPromise = this._doInit();
340
+ return this._initPromise;
341
+ }
342
+ async _doInit() {
343
+ this._brain = await createBrain(this._repoPath);
344
+ await this._brain.initialize();
345
+ this._sources = [];
346
+ const seen = /* @__PURE__ */ new Set();
347
+ for (const name of this._brain.plugins) {
348
+ const base = name.split(":")[0];
349
+ if (seen.has(base)) continue;
350
+ seen.add(base);
351
+ this._sources.push({
352
+ key: base,
353
+ label: base.charAt(0).toUpperCase() + base.slice(1),
354
+ enabled: true,
355
+ k: 20
356
+ });
357
+ }
358
+ }
359
+ /**
360
+ * Run the full search pipeline and return staged results.
361
+ *
362
+ * @param query - Search query
363
+ * @param activeSourceKeys - Set of active source keys (e.g. {'code', 'git'})
364
+ * @param usePruner - Whether to run the pruner stage
365
+ * @param useExpander - Whether to run the expander stage
366
+ */
367
+ async search(query, activeSourceKeys, usePruner = true, useExpander = true) {
368
+ if (!this._brain) throw new Error("BrainSearchSession not initialized");
369
+ const tTotal = Date.now();
370
+ const pruner = usePruner ? this._brain.config.pruner : void 0;
371
+ const expander = useExpander ? this._brain.config.expander : void 0;
372
+ const sources = {};
373
+ for (const src of this._sources) {
374
+ const enabled = activeSourceKeys ? activeSourceKeys.has(src.key) : src.enabled;
375
+ sources[src.key] = enabled ? src.k : 0;
376
+ }
377
+ const tSearch0 = Date.now();
378
+ const raw = await this._brain.search(query, {
379
+ sources,
380
+ source: "cli"
381
+ });
382
+ const tSearch = Date.now() - tSearch0;
383
+ let pruned = raw;
384
+ let dropped = [];
385
+ let tPrune = 0;
386
+ if (pruner && raw.length > 1) {
387
+ const pt0 = Date.now();
388
+ pruned = await pruneResults(query, raw, pruner);
389
+ tPrune = Date.now() - pt0;
390
+ const prunedSet = new Set(pruned);
391
+ dropped = raw.filter((r) => !prunedSet.has(r));
392
+ }
393
+ let expanded = [];
394
+ let tExpand = 0;
395
+ if (expander && pruned.length > 0) {
396
+ const et0 = Date.now();
397
+ let expandedRaw = await this._runExpansion(query, pruned, expander);
398
+ if (activeSourceKeys) {
399
+ expandedRaw = expandedRaw.filter((r) => activeSourceKeys.has(r.type));
400
+ }
401
+ expanded = expandedRaw;
402
+ tExpand = Date.now() - et0;
403
+ }
404
+ return {
405
+ raw,
406
+ pruned,
407
+ dropped,
408
+ expanded,
409
+ timings: {
410
+ init: 0,
411
+ search: tSearch,
412
+ prune: tPrune,
413
+ expand: tExpand,
414
+ total: Date.now() - tTotal
415
+ },
416
+ prunerName: usePruner ? pruner?.constructor?.name ?? null : null,
417
+ expanderName: useExpander ? expander?.constructor?.name ?? null : null
418
+ };
419
+ }
420
+ /** Run LLM expansion — mirrors ContextBuilder._expand logic. */
421
+ async _runExpansion(query, results, expander) {
422
+ if (!this._brain) return [];
423
+ const excludeFilePaths = [...new Set(
424
+ results.filter((r) => r.filePath).map((r) => r.filePath)
425
+ )];
426
+ const excludeIds = [];
427
+ for (const r of results) {
428
+ const meta = r.metadata;
429
+ const id = meta?.id;
430
+ if (id !== void 0) excludeIds.push(id);
431
+ }
432
+ const manifest = [];
433
+ let resolver;
434
+ const registry = this._brain._registry;
435
+ if (registry) {
436
+ for (const mod of registry.all) {
437
+ if (!isExpandablePlugin(mod)) continue;
438
+ const plugin = mod;
439
+ manifest.push(...plugin.buildManifest(excludeFilePaths, excludeIds, excludeFilePaths));
440
+ if (!resolver) {
441
+ resolver = /* @__PURE__ */ __name((ids) => plugin.resolveChunks(ids), "resolver");
442
+ }
443
+ }
444
+ }
445
+ if (manifest.length === 0 || !resolver) return [];
446
+ try {
447
+ const expandResult = await expander.expand(query, excludeIds, manifest);
448
+ if (expandResult.ids.length === 0) return [];
449
+ return resolver(expandResult.ids);
450
+ } catch {
451
+ return [];
452
+ }
453
+ }
454
+ /** Cleanup. */
455
+ close() {
456
+ if (this._brain) {
457
+ this._brain.close();
458
+ this._brain = null;
459
+ }
460
+ this._initPromise = null;
461
+ }
462
+ };
463
+
464
+ // src/cli/tui/stats-tui.tsx
465
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
466
+ var C = {
467
+ aurora: "#7AA2F7",
468
+ success: "#9ECE6A",
469
+ error: "#F7768E",
470
+ warning: "#E0AF68",
471
+ dim: "#565F89",
472
+ text: "#C0CAF5",
473
+ border: "#3B4261",
474
+ cyan: "#7DCFFF",
475
+ purple: "#BB9AF7",
476
+ orange: "#FF9E64",
477
+ dir: "#E0AF68"
478
+ };
479
+ var LANG_COLORS = {
480
+ python: "#4B8BBE",
481
+ typescript: "#519ABA",
482
+ javascript: "#CBCB41",
483
+ css: "#42A5F5",
484
+ go: "#7FD5EA",
485
+ rust: "#DEA584",
486
+ ruby: "#CC3E44",
487
+ java: "#CC3E44",
488
+ c: "#599EFF",
489
+ cpp: "#599EFF"
490
+ };
491
+ var LANG_BADGES = {
492
+ python: "PY",
493
+ typescript: "TS",
494
+ javascript: "JS",
495
+ css: "CS",
496
+ go: "GO",
497
+ rust: "RS",
498
+ ruby: "RB",
499
+ java: "JV",
500
+ c: "C ",
501
+ cpp: "C+"
502
+ };
503
+ function langColor(lang) {
504
+ return LANG_COLORS[lang] ?? C.text;
505
+ }
506
+ __name(langColor, "langColor");
507
+ function langBadge(lang) {
508
+ return LANG_BADGES[lang] ?? lang.slice(0, 2).toUpperCase();
509
+ }
510
+ __name(langBadge, "langBadge");
511
+ function centerScroll(cursor, total, viewH) {
512
+ if (total <= viewH) return 0;
513
+ const half = Math.floor(viewH / 2);
514
+ const offset = Math.max(0, cursor - half);
515
+ return Math.min(offset, total - viewH);
516
+ }
517
+ __name(centerScroll, "centerScroll");
518
+ function bar(percent, width) {
519
+ const filled = Math.round(percent / 100 * width);
520
+ return "\u2588".repeat(filled) + "\u2591".repeat(Math.max(0, width - filled));
521
+ }
522
+ __name(bar, "bar");
523
+ function truncate(str, max) {
524
+ return str.length > max ? str.slice(0, max - 1) + "\u2026" : str;
525
+ }
526
+ __name(truncate, "truncate");
527
+ var KEYWORDS = /* @__PURE__ */ new Set([
528
+ // JS/TS
529
+ "async",
530
+ "await",
531
+ "break",
532
+ "case",
533
+ "catch",
534
+ "class",
535
+ "const",
536
+ "continue",
537
+ "default",
538
+ "delete",
539
+ "do",
540
+ "else",
541
+ "enum",
542
+ "export",
543
+ "extends",
544
+ "false",
545
+ "finally",
546
+ "for",
547
+ "from",
548
+ "function",
549
+ "if",
550
+ "implements",
551
+ "import",
552
+ "in",
553
+ "instanceof",
554
+ "interface",
555
+ "let",
556
+ "new",
557
+ "null",
558
+ "of",
559
+ "return",
560
+ "static",
561
+ "super",
562
+ "switch",
563
+ "this",
564
+ "throw",
565
+ "true",
566
+ "try",
567
+ "type",
568
+ "typeof",
569
+ "undefined",
570
+ "var",
571
+ "void",
572
+ "while",
573
+ "yield",
574
+ // Python
575
+ "def",
576
+ "class",
577
+ "self",
578
+ "None",
579
+ "True",
580
+ "False",
581
+ "and",
582
+ "or",
583
+ "not",
584
+ "is",
585
+ "lambda",
586
+ "with",
587
+ "as",
588
+ "pass",
589
+ "raise",
590
+ "global",
591
+ "nonlocal",
592
+ "elif",
593
+ "except",
594
+ "assert"
595
+ ]);
596
+ var TYPE_WORDS = /* @__PURE__ */ new Set([
597
+ "string",
598
+ "number",
599
+ "boolean",
600
+ "any",
601
+ "void",
602
+ "never",
603
+ "unknown",
604
+ "object",
605
+ "Promise",
606
+ "Array",
607
+ "Map",
608
+ "Set",
609
+ "Record",
610
+ "Partial",
611
+ "Readonly",
612
+ "Required",
613
+ "Pick",
614
+ "Omit",
615
+ "int",
616
+ "float",
617
+ "str",
618
+ "list",
619
+ "dict",
620
+ "tuple",
621
+ "bool",
622
+ "Optional"
623
+ ]);
624
+ function highlightLine(line, maxW) {
625
+ const trimmed = line.length > maxW ? line.slice(0, maxW - 1) + "\u2026" : line;
626
+ if (trimmed.length === 0) return [{ text: "", color: C.text }];
627
+ const segments = [];
628
+ const commentIdx = findCommentStart(trimmed);
629
+ const codePart = commentIdx >= 0 ? trimmed.slice(0, commentIdx) : trimmed;
630
+ const commentPart = commentIdx >= 0 ? trimmed.slice(commentIdx) : "";
631
+ if (codePart.length > 0) {
632
+ const pattern = /(@\w+)|("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|`(?:[^`\\]|\\.)*`)|(\b\d+(?:\.\d+)?\b)|(\b[A-Za-z_]\w*\b)|([^A-Za-z_@'"\d`]+)/g;
633
+ let m;
634
+ while ((m = pattern.exec(codePart)) !== null) {
635
+ const [full, decorator, str, num, word, other] = m;
636
+ if (decorator) {
637
+ segments.push({ text: decorator, color: C.warning });
638
+ } else if (str) {
639
+ segments.push({ text: str, color: C.success });
640
+ } else if (num) {
641
+ segments.push({ text: num, color: C.orange });
642
+ } else if (word) {
643
+ if (KEYWORDS.has(word)) {
644
+ segments.push({ text: word, color: C.purple });
645
+ } else if (TYPE_WORDS.has(word)) {
646
+ segments.push({ text: word, color: C.cyan });
647
+ } else if (word[0] === word[0].toUpperCase() && word[0] !== word[0].toLowerCase()) {
648
+ segments.push({ text: word, color: C.cyan });
649
+ } else {
650
+ segments.push({ text: word, color: C.text });
651
+ }
652
+ } else if (other) {
653
+ segments.push({ text: other, color: C.dim });
654
+ } else {
655
+ segments.push({ text: full, color: C.text });
656
+ }
657
+ }
658
+ }
659
+ if (commentPart) {
660
+ segments.push({ text: commentPart, color: C.dim });
661
+ }
662
+ return segments.length > 0 ? segments : [{ text: trimmed, color: C.text }];
663
+ }
664
+ __name(highlightLine, "highlightLine");
665
+ function findCommentStart(line) {
666
+ let inString = null;
667
+ for (let i = 0; i < line.length; i++) {
668
+ const ch = line[i];
669
+ if (inString) {
670
+ if (ch === "\\") {
671
+ i++;
672
+ continue;
673
+ }
674
+ if (ch === inString) inString = null;
675
+ } else {
676
+ if (ch === '"' || ch === "'" || ch === "`") {
677
+ inString = ch;
678
+ continue;
679
+ }
680
+ if (ch === "/" && line[i + 1] === "/") return i;
681
+ if (ch === "#" && (i === 0 || /\s/.test(line[i - 1]))) return i;
682
+ }
683
+ }
684
+ return -1;
685
+ }
686
+ __name(findCommentStart, "findCommentStart");
687
+ function HighlightedLine({ segments }) {
688
+ return /* @__PURE__ */ jsx(Fragment, { children: segments.map((seg, i) => /* @__PURE__ */ jsx(Text, { color: seg.color, children: seg.text }, i)) });
689
+ }
690
+ __name(HighlightedLine, "HighlightedLine");
691
+ function DashboardView({ overview, languages, dirs, width, height, onDrillDir, onCallGraph, onSearch }) {
692
+ const [cursor, setCursor] = useState(0);
693
+ useInput((input, key) => {
694
+ if (key.downArrow) setCursor((c) => Math.min(c + 1, dirs.length - 1));
695
+ if (key.upArrow) setCursor((c) => Math.max(c - 1, 0));
696
+ if (key.return && dirs[cursor]) onDrillDir(dirs[cursor].dir);
697
+ if (input === "g") onCallGraph();
698
+ if (input === "/") onSearch();
699
+ });
700
+ const leftW = 30;
701
+ const rightW = Math.max(40, width - leftW - 5);
702
+ const barW = Math.max(10, rightW - 35);
703
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", width, height: height - 4, children: [
704
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "column", width: leftW, paddingX: 1, children: [
705
+ /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(Text, { color: C.aurora, bold: true, children: "Overview" }) }),
706
+ /* @__PURE__ */ jsxs(Text, { color: C.text, children: [
707
+ " \u{1F4C1} ",
708
+ /* @__PURE__ */ jsx(Text, { bold: true, children: overview.files }),
709
+ " files"
710
+ ] }),
711
+ /* @__PURE__ */ jsxs(Text, { color: C.text, children: [
712
+ " \u{1F9E9} ",
713
+ /* @__PURE__ */ jsx(Text, { bold: true, children: overview.chunks }),
714
+ " chunks"
715
+ ] }),
716
+ /* @__PURE__ */ jsxs(Text, { color: C.text, children: [
717
+ " \u{1F517} ",
718
+ /* @__PURE__ */ jsx(Text, { bold: true, children: overview.callEdges }),
719
+ " call edges"
720
+ ] }),
721
+ /* @__PURE__ */ jsxs(Text, { color: C.text, children: [
722
+ " \u{1F4E5} ",
723
+ /* @__PURE__ */ jsx(Text, { bold: true, children: overview.importEdges }),
724
+ " imports"
725
+ ] }),
726
+ /* @__PURE__ */ jsxs(Text, { color: C.text, children: [
727
+ " \u{1F3F7} ",
728
+ /* @__PURE__ */ jsx(Text, { bold: true, children: overview.symbols }),
729
+ " symbols"
730
+ ] }),
731
+ /* @__PURE__ */ jsxs(Text, { color: C.text, children: [
732
+ " \u{1F4CA} ",
733
+ /* @__PURE__ */ jsx(Text, { bold: true, children: overview.dbSizeMB }),
734
+ " MB db"
735
+ ] }),
736
+ /* @__PURE__ */ jsxs(Text, { color: C.text, children: [
737
+ " \u{1F50D} ",
738
+ /* @__PURE__ */ jsx(Text, { bold: true, children: overview.hnswSize }),
739
+ " vectors"
740
+ ] }),
741
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { color: C.dim, children: "Embedding:" }) }),
742
+ /* @__PURE__ */ jsxs(Text, { color: C.cyan, children: [
743
+ " ",
744
+ overview.embeddingModel
745
+ ] }),
746
+ /* @__PURE__ */ jsxs(Text, { color: C.dim, children: [
747
+ " Pruner: ",
748
+ /* @__PURE__ */ jsx(Text, { color: C.text, children: overview.pruner })
749
+ ] }),
750
+ /* @__PURE__ */ jsxs(Text, { color: C.dim, children: [
751
+ " Expander: ",
752
+ /* @__PURE__ */ jsx(Text, { color: C.text, children: overview.expander })
753
+ ] })
754
+ ] }),
755
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "column", width: rightW, paddingX: 1, children: [
756
+ /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(Text, { color: C.aurora, bold: true, children: "Language Breakdown" }) }),
757
+ languages.map((lang) => /* @__PURE__ */ jsx(Box, { height: 1, children: /* @__PURE__ */ jsxs(Text, { wrap: "truncate", children: [
758
+ /* @__PURE__ */ jsx(Text, { color: langColor(lang.language), bold: true, children: langBadge(lang.language) }),
759
+ /* @__PURE__ */ jsx(Text, { children: " " }),
760
+ /* @__PURE__ */ jsx(Text, { color: langColor(lang.language), children: bar(lang.percent, barW) }),
761
+ /* @__PURE__ */ jsxs(Text, { color: C.dim, children: [
762
+ " ",
763
+ String(lang.chunks).padStart(4),
764
+ " "
765
+ ] }),
766
+ /* @__PURE__ */ jsxs(Text, { color: C.dim, children: [
767
+ lang.percent.toFixed(1).padStart(5),
768
+ "%"
769
+ ] })
770
+ ] }) }, lang.language)),
771
+ /* @__PURE__ */ jsxs(Box, { marginTop: 1, marginBottom: 1, children: [
772
+ /* @__PURE__ */ jsx(Text, { color: C.aurora, bold: true, children: "Directories" }),
773
+ /* @__PURE__ */ jsx(Text, { color: C.dim, children: " \u2500\u2500\u2500 files \u2500\u2500 chunks" })
774
+ ] }),
775
+ dirs.map((d, i) => {
776
+ const isCursor = i === cursor;
777
+ const ptr = isCursor ? "\u25B8 " : " ";
778
+ const dirBarW = Math.max(5, Math.min(15, Math.round(d.percent / 100 * 15)));
779
+ return /* @__PURE__ */ jsx(Box, { height: 1, children: /* @__PURE__ */ jsxs(Text, { wrap: "truncate", children: [
780
+ /* @__PURE__ */ jsx(Text, { color: isCursor ? C.aurora : C.dim, children: ptr }),
781
+ /* @__PURE__ */ jsx(Text, { color: isCursor ? C.aurora : C.dir, bold: isCursor, children: truncate(d.dir + "/", 20).padEnd(20) }),
782
+ /* @__PURE__ */ jsxs(Text, { color: C.dim, children: [
783
+ String(d.files).padStart(5),
784
+ " ",
785
+ String(d.chunks).padStart(5),
786
+ " "
787
+ ] }),
788
+ /* @__PURE__ */ jsx(Text, { color: isCursor ? C.aurora : C.success, children: bar(d.percent, dirBarW) })
789
+ ] }) }, d.dir);
790
+ })
791
+ ] })
792
+ ] });
793
+ }
794
+ __name(DashboardView, "DashboardView");
795
+ function FileExplorerView({ dbPath, dir, width, height, onDrillFile, onBack }) {
796
+ const files = useMemo(() => fetchFilesInDir(dbPath, dir), [dbPath, dir]);
797
+ const [cursor, setCursor] = useState(0);
798
+ const [sortMode, setSortMode] = useState("chunks");
799
+ const [filterText, setFilterText] = useState("");
800
+ const isFilteringRef = useRef(false);
801
+ const [isFiltering, setIsFiltering] = useState(false);
802
+ const sorted = useMemo(() => {
803
+ const s = [...files];
804
+ if (sortMode === "name") s.sort((a, b) => a.fileName.localeCompare(b.fileName));
805
+ else if (sortMode === "symbols") s.sort((a, b) => b.symbols - a.symbols);
806
+ return s;
807
+ }, [files, sortMode]);
808
+ const filtered = useMemo(() => {
809
+ if (!filterText) return sorted;
810
+ const lower = filterText.toLowerCase();
811
+ return sorted.filter((f) => f.fileName.toLowerCase().includes(lower));
812
+ }, [sorted, filterText]);
813
+ const detail = useMemo(() => {
814
+ if (!filtered[cursor]) return null;
815
+ return fetchFileDetail(dbPath, filtered[cursor].filePath);
816
+ }, [dbPath, filtered, cursor]);
817
+ const listH = Math.max(5, height - 6);
818
+ const scrollOff = centerScroll(cursor, filtered.length, listH);
819
+ useInput((input, key) => {
820
+ const filtering = isFilteringRef.current;
821
+ if (key.escape) {
822
+ if (filtering || filterText) {
823
+ isFilteringRef.current = false;
824
+ setIsFiltering(false);
825
+ setFilterText("");
826
+ setCursor(0);
827
+ return;
828
+ }
829
+ onBack();
830
+ return;
831
+ }
832
+ if (filtering) {
833
+ if (key.return) {
834
+ isFilteringRef.current = false;
835
+ setIsFiltering(false);
836
+ return;
837
+ }
838
+ if (key.backspace || key.delete) {
839
+ setFilterText((prev) => prev.slice(0, -1));
840
+ setCursor(0);
841
+ return;
842
+ }
843
+ if (input && !key.upArrow && !key.downArrow) {
844
+ setFilterText((prev) => prev + input);
845
+ setCursor(0);
846
+ return;
847
+ }
848
+ }
849
+ if (input === "/" && !filtering) {
850
+ isFilteringRef.current = true;
851
+ setIsFiltering(true);
852
+ setFilterText("");
853
+ setCursor(0);
854
+ return;
855
+ }
856
+ if (key.downArrow) setCursor((c) => Math.min(c + 1, filtered.length - 1));
857
+ if (key.upArrow) setCursor((c) => Math.max(c - 1, 0));
858
+ if (key.return && filtered[cursor]) onDrillFile(filtered[cursor].filePath);
859
+ if (!filtering && input === "s") setSortMode((m) => m === "chunks" ? "name" : m === "name" ? "symbols" : "chunks");
860
+ });
861
+ const leftW = Math.min(40, Math.floor(width * 0.4));
862
+ const rightW = width - leftW - 3;
863
+ const visible = filtered.slice(scrollOff, scrollOff + listH);
864
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", width, height: height - 4, children: [
865
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "column", width: leftW, paddingX: 1, children: [
866
+ /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
867
+ /* @__PURE__ */ jsx(Text, { color: C.aurora, bold: true, children: "Files" }),
868
+ /* @__PURE__ */ jsxs(Text, { color: C.dim, children: [
869
+ " (",
870
+ filtered.length,
871
+ filterText ? `/${files.length}` : "",
872
+ ")"
873
+ ] }),
874
+ /* @__PURE__ */ jsx(Text, { color: C.dim, children: " sort: " }),
875
+ /* @__PURE__ */ jsx(Text, { color: C.cyan, children: sortMode })
876
+ ] }),
877
+ (isFiltering || filterText) && /* @__PURE__ */ jsxs(Box, { height: 1, marginBottom: 0, children: [
878
+ /* @__PURE__ */ jsx(Text, { color: C.aurora, bold: true, children: "/ " }),
879
+ /* @__PURE__ */ jsx(Text, { color: C.text, children: filterText }),
880
+ /* @__PURE__ */ jsx(Text, { color: C.aurora, children: "\u258E" })
881
+ ] }),
882
+ visible.map((f, vi) => {
883
+ const idx = scrollOff + vi;
884
+ const isCursor = idx === cursor;
885
+ const ptr = isCursor ? "\u25B8 " : " ";
886
+ return /* @__PURE__ */ jsx(Box, { height: 1, children: /* @__PURE__ */ jsxs(Text, { wrap: "truncate", children: [
887
+ /* @__PURE__ */ jsx(Text, { color: isCursor ? C.aurora : C.dim, children: ptr }),
888
+ /* @__PURE__ */ jsx(Text, { color: langColor(f.language), bold: true, children: langBadge(f.language) }),
889
+ /* @__PURE__ */ jsx(Text, { children: " " }),
890
+ /* @__PURE__ */ jsx(Text, { color: isCursor ? C.text : C.dim, children: truncate(f.fileName, leftW - 12) }),
891
+ /* @__PURE__ */ jsxs(Text, { color: C.dim, children: [
892
+ " ",
893
+ String(f.chunks).padStart(3),
894
+ "ch"
895
+ ] })
896
+ ] }) }, f.filePath);
897
+ })
898
+ ] }),
899
+ /* @__PURE__ */ jsx(Box, { flexDirection: "column", width: rightW, paddingX: 1, children: detail && /* @__PURE__ */ jsxs(Fragment, { children: [
900
+ /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(Text, { color: C.aurora, bold: true, children: "File Detail" }) }),
901
+ /* @__PURE__ */ jsxs(Text, { color: C.dim, children: [
902
+ "\u{1F4C4} ",
903
+ /* @__PURE__ */ jsx(Text, { color: C.text, children: detail.filePath })
904
+ ] }),
905
+ /* @__PURE__ */ jsxs(Text, { color: C.dim, children: [
906
+ " Language: ",
907
+ /* @__PURE__ */ jsx(Text, { color: langColor(detail.language), children: detail.language })
908
+ ] }),
909
+ /* @__PURE__ */ jsxs(Text, { color: C.dim, children: [
910
+ " Chunks: ",
911
+ /* @__PURE__ */ jsx(Text, { color: C.text, children: detail.chunks })
912
+ ] }),
913
+ /* @__PURE__ */ jsxs(Text, { color: C.dim, children: [
914
+ " Symbols: ",
915
+ /* @__PURE__ */ jsx(Text, { color: C.text, children: detail.symbols.length })
916
+ ] }),
917
+ /* @__PURE__ */ jsxs(Text, { color: C.dim, children: [
918
+ " Imports: ",
919
+ /* @__PURE__ */ jsxs(Text, { color: C.success, children: [
920
+ detail.importsIn.length,
921
+ " in"
922
+ ] }),
923
+ ", ",
924
+ /* @__PURE__ */ jsxs(Text, { color: C.orange, children: [
925
+ detail.importsOut.length,
926
+ " out"
927
+ ] })
928
+ ] }),
929
+ /* @__PURE__ */ jsxs(Text, { color: C.dim, children: [
930
+ " Call edges: ",
931
+ /* @__PURE__ */ jsxs(Text, { color: C.success, children: [
932
+ detail.callEdgesIn,
933
+ " in"
934
+ ] }),
935
+ ", ",
936
+ /* @__PURE__ */ jsxs(Text, { color: C.orange, children: [
937
+ detail.callEdgesOut,
938
+ " out"
939
+ ] })
940
+ ] }),
941
+ detail.symbols.length > 0 && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
942
+ /* @__PURE__ */ jsx(Text, { color: C.purple, bold: true, children: "Symbols" }),
943
+ detail.symbols.slice(0, Math.max(5, height - 15)).map((sym, i) => /* @__PURE__ */ jsx(Box, { height: 1, children: /* @__PURE__ */ jsxs(Text, { wrap: "truncate", children: [
944
+ /* @__PURE__ */ jsxs(Text, { color: C.dim, children: [
945
+ " ",
946
+ sym.kind === "class" || sym.kind === "Class" ? "C" : "\u0192",
947
+ " "
948
+ ] }),
949
+ /* @__PURE__ */ jsx(Text, { color: C.text, children: truncate(sym.name, rightW - 10) }),
950
+ /* @__PURE__ */ jsxs(Text, { color: C.dim, children: [
951
+ " L",
952
+ sym.line
953
+ ] })
954
+ ] }) }, `${sym.name}-${i}`)),
955
+ detail.symbols.length > height - 15 && /* @__PURE__ */ jsxs(Text, { color: C.dim, children: [
956
+ " \u2026 ",
957
+ detail.symbols.length - (height - 15),
958
+ " more"
959
+ ] })
960
+ ] }),
961
+ detail.importsIn.length > 0 && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
962
+ /* @__PURE__ */ jsx(Text, { color: C.cyan, bold: true, children: "Imported by" }),
963
+ detail.importsIn.slice(0, 5).map((imp, i) => /* @__PURE__ */ jsxs(Text, { color: C.dim, children: [
964
+ " \u2190 ",
965
+ truncate(imp, rightW - 6)
966
+ ] }, i))
967
+ ] })
968
+ ] }) })
969
+ ] });
970
+ }
971
+ __name(FileExplorerView, "FileExplorerView");
972
+ function ChunkViewerView({ dbPath, filePath, width, height, onBack }) {
973
+ const chunks = useMemo(() => fetchChunksForFile(dbPath, filePath), [dbPath, filePath]);
974
+ const [cursor, setCursor] = useState(0);
975
+ const [contentScroll, setContentScroll] = useState(0);
976
+ const [focusPanel, setFocusPanel] = useState("list");
977
+ const listH = Math.max(5, height - 6);
978
+ const scrollOff = centerScroll(cursor, chunks.length, listH);
979
+ const leftW = Math.min(26, Math.floor(width * 0.28));
980
+ const rightW = width - leftW - 3;
981
+ const activeChunk = chunks[cursor] ?? null;
982
+ const visible = chunks.slice(scrollOff, scrollOff + listH);
983
+ const previewH = Math.max(3, height - 9);
984
+ const contentLines = useMemo(() => activeChunk?.content.split("\n") ?? [], [activeChunk]);
985
+ const maxContentScroll = Math.max(0, contentLines.length - previewH);
986
+ useEffect(() => {
987
+ setContentScroll(0);
988
+ }, [cursor]);
989
+ useInput((input, key) => {
990
+ if (key.escape) {
991
+ if (focusPanel === "content") {
992
+ setFocusPanel("list");
993
+ return;
994
+ }
995
+ onBack();
996
+ }
997
+ if (key.tab || input === "l" && focusPanel === "list" || input === "h" && focusPanel === "content" || key.rightArrow && focusPanel === "list" || key.leftArrow && focusPanel === "content") {
998
+ setFocusPanel((p) => p === "list" ? "content" : "list");
999
+ return;
1000
+ }
1001
+ if (focusPanel === "list") {
1002
+ if (key.downArrow) setCursor((c) => Math.min(c + 1, chunks.length - 1));
1003
+ if (key.upArrow) setCursor((c) => Math.max(c - 1, 0));
1004
+ if (input === "}") setCursor((c) => Math.min(c + 10, chunks.length - 1));
1005
+ if (input === "{") setCursor((c) => Math.max(c - 10, 0));
1006
+ if (key.return) setFocusPanel("content");
1007
+ } else {
1008
+ if (key.downArrow) setContentScroll((s) => Math.min(s + 1, maxContentScroll));
1009
+ if (key.upArrow) setContentScroll((s) => Math.max(s - 1, 0));
1010
+ if (input === "}") setContentScroll((s) => Math.min(s + 10, maxContentScroll));
1011
+ if (input === "{") setContentScroll((s) => Math.max(s - 10, 0));
1012
+ if (input === "d") setContentScroll((s) => Math.min(s + 15, maxContentScroll));
1013
+ if (input === "u") setContentScroll((s) => Math.max(s - 15, 0));
1014
+ }
1015
+ });
1016
+ const visibleLines = contentLines.slice(contentScroll, contentScroll + previewH);
1017
+ const scrollPct = maxContentScroll > 0 ? Math.round(contentScroll / maxContentScroll * 100) : 100;
1018
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", width, height: height - 4, children: [
1019
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "column", width: leftW, paddingX: 1, children: [
1020
+ /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
1021
+ /* @__PURE__ */ jsx(Text, { color: focusPanel === "list" ? C.aurora : C.dim, bold: true, children: "Chunks" }),
1022
+ /* @__PURE__ */ jsxs(Text, { color: C.dim, children: [
1023
+ " (",
1024
+ chunks.length,
1025
+ ")"
1026
+ ] })
1027
+ ] }),
1028
+ visible.map((ch, vi) => {
1029
+ const idx = scrollOff + vi;
1030
+ const isCursor = idx === cursor;
1031
+ const ptr = isCursor ? "\u25B8 " : " ";
1032
+ const hasSym = ch.name !== null && ch.name !== "";
1033
+ const active = focusPanel === "list" && isCursor;
1034
+ return /* @__PURE__ */ jsx(Box, { height: 1, children: /* @__PURE__ */ jsxs(Text, { wrap: "truncate", children: [
1035
+ /* @__PURE__ */ jsx(Text, { color: active ? C.aurora : isCursor ? C.cyan : C.dim, children: ptr }),
1036
+ /* @__PURE__ */ jsxs(Text, { color: active ? C.text : isCursor ? C.cyan : C.dim, children: [
1037
+ "#",
1038
+ String(idx + 1).padStart(2),
1039
+ " L",
1040
+ ch.startLine,
1041
+ "-",
1042
+ ch.endLine
1043
+ ] }),
1044
+ hasSym && /* @__PURE__ */ jsx(Text, { color: C.warning, children: " \u2605" })
1045
+ ] }) }, ch.id);
1046
+ }),
1047
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { color: C.dim, children: "\u2605 = named symbol" }) })
1048
+ ] }),
1049
+ /* @__PURE__ */ jsx(Box, { flexDirection: "column", width: rightW, paddingX: 1, children: activeChunk && /* @__PURE__ */ jsxs(Fragment, { children: [
1050
+ /* @__PURE__ */ jsxs(Box, { marginBottom: 0, justifyContent: "space-between", children: [
1051
+ /* @__PURE__ */ jsxs(Text, { children: [
1052
+ /* @__PURE__ */ jsx(Text, { color: focusPanel === "content" ? C.aurora : C.dim, bold: true, children: "Preview" }),
1053
+ /* @__PURE__ */ jsxs(Text, { color: C.dim, children: [
1054
+ " #",
1055
+ cursor + 1,
1056
+ " L",
1057
+ activeChunk.startLine,
1058
+ "-",
1059
+ activeChunk.endLine
1060
+ ] }),
1061
+ activeChunk.name ? /* @__PURE__ */ jsxs(Text, { color: C.purple, children: [
1062
+ " ",
1063
+ activeChunk.name
1064
+ ] }) : null
1065
+ ] }),
1066
+ /* @__PURE__ */ jsxs(Text, { children: [
1067
+ focusPanel === "list" && /* @__PURE__ */ jsx(Text, { color: C.dim, italic: true, children: "Enter to scroll " }),
1068
+ contentLines.length > previewH && /* @__PURE__ */ jsxs(Text, { color: focusPanel === "content" ? C.cyan : C.dim, children: [
1069
+ scrollPct,
1070
+ "%"
1071
+ ] })
1072
+ ] })
1073
+ ] }),
1074
+ /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: visibleLines.map((line, i) => {
1075
+ const segs = highlightLine(line, rightW - 6);
1076
+ return /* @__PURE__ */ jsx(Box, { height: 1, children: /* @__PURE__ */ jsxs(Text, { wrap: "truncate", children: [
1077
+ /* @__PURE__ */ jsxs(Text, { color: C.dim, children: [
1078
+ String(activeChunk.startLine + contentScroll + i).padStart(4),
1079
+ "\u2502"
1080
+ ] }),
1081
+ /* @__PURE__ */ jsx(HighlightedLine, { segments: segs })
1082
+ ] }) }, contentScroll + i);
1083
+ }) }),
1084
+ /* @__PURE__ */ jsxs(Box, { marginTop: 1, flexDirection: "column", children: [
1085
+ activeChunk.callsOut.length > 0 && /* @__PURE__ */ jsxs(Text, { color: C.dim, children: [
1086
+ "\u2192 ",
1087
+ /* @__PURE__ */ jsx(Text, { color: C.orange, children: activeChunk.callsOut.slice(0, 8).join(", ") })
1088
+ ] }),
1089
+ activeChunk.calledBy.length > 0 && /* @__PURE__ */ jsxs(Text, { color: C.dim, children: [
1090
+ "\u2190 ",
1091
+ /* @__PURE__ */ jsx(Text, { color: C.success, children: activeChunk.calledBy.slice(0, 8).join(", ") })
1092
+ ] })
1093
+ ] })
1094
+ ] }) })
1095
+ ] });
1096
+ }
1097
+ __name(ChunkViewerView, "ChunkViewerView");
1098
+ function resultLabel(r) {
1099
+ const meta = r.metadata;
1100
+ return {
1101
+ name: meta?.name ?? r.type,
1102
+ path: r.filePath ?? "unknown",
1103
+ score: r.score,
1104
+ line: meta?.startLine ?? 0
1105
+ };
1106
+ }
1107
+ __name(resultLabel, "resultLabel");
1108
+ function SemanticSearchView({ repoPath, width, height, onBack, session }) {
1109
+ const sessionRef = useRef(session);
1110
+ const [query, setQuery] = useState("");
1111
+ const [state, setState] = useState(
1112
+ () => session.initialized ? "idle" : "initializing"
1113
+ );
1114
+ const [stateMsg, setStateMsg] = useState(
1115
+ () => session.initialized ? "" : "Loading search index..."
1116
+ );
1117
+ const [focus, setFocus] = useState("input");
1118
+ const [pipeline, setPipeline] = useState(null);
1119
+ const [sourceOpts, setSourceOpts] = useState(
1120
+ () => session.initialized ? [...session.sources] : []
1121
+ );
1122
+ const [rawCursor, setRawCursor] = useState(0);
1123
+ const [finalCursor, setFinalCursor] = useState(0);
1124
+ const [previewScroll, setPreviewScroll] = useState(0);
1125
+ const [errorMsg, setErrorMsg] = useState("");
1126
+ const [sourceCursor, setSourceCursor] = useState(0);
1127
+ const [usePruner, setUsePruner] = useState(true);
1128
+ const [useExpander, setUseExpander] = useState(true);
1129
+ useEffect(() => {
1130
+ sessionRef.current = session;
1131
+ if (session.initialized) {
1132
+ setSourceOpts([...session.sources]);
1133
+ setState("idle");
1134
+ setStateMsg("");
1135
+ return;
1136
+ }
1137
+ setState("initializing");
1138
+ setStateMsg("Loading search index...");
1139
+ const timer = setTimeout(() => {
1140
+ session.init().then(() => {
1141
+ setSourceOpts([...session.sources]);
1142
+ setState("idle");
1143
+ setStateMsg("");
1144
+ }).catch((err) => {
1145
+ setState("error");
1146
+ setErrorMsg(err instanceof Error ? err.message : String(err));
1147
+ });
1148
+ }, 50);
1149
+ return () => {
1150
+ clearTimeout(timer);
1151
+ };
1152
+ }, [session]);
1153
+ const rawW = Math.max(20, Math.floor(width * 0.22));
1154
+ const prunedW = Math.max(20, Math.floor(width * 0.22));
1155
+ const previewW = width - rawW - prunedW - 4;
1156
+ const listH = Math.max(3, height - 12);
1157
+ const previewH = Math.max(3, height - 12);
1158
+ const activeResult = useMemo(() => {
1159
+ if (!pipeline) return null;
1160
+ if (focus === "final") {
1161
+ const combined = [...pipeline.pruned, ...pipeline.expanded];
1162
+ return combined[finalCursor] ?? null;
1163
+ }
1164
+ return pipeline.raw[rawCursor] ?? null;
1165
+ }, [focus, rawCursor, finalCursor, pipeline]);
1166
+ const previewLines = useMemo(() => activeResult?.content.split("\n") ?? [], [activeResult]);
1167
+ const maxPreviewScroll = Math.max(0, previewLines.length - previewH);
1168
+ useEffect(() => {
1169
+ setPreviewScroll(0);
1170
+ }, [activeResult]);
1171
+ const toggleSource = useCallback((key) => {
1172
+ setSourceOpts((prev) => {
1173
+ const next = prev.map((s) => ({ ...s }));
1174
+ const target = next.find((s) => s.key === key);
1175
+ if (target) target.enabled = !target.enabled;
1176
+ return next;
1177
+ });
1178
+ }, []);
1179
+ const doSearch = useCallback(async () => {
1180
+ const session2 = sessionRef.current;
1181
+ if (!session2?.initialized || !query.trim()) return;
1182
+ setState("searching");
1183
+ setStateMsg("Searching...");
1184
+ setRawCursor(0);
1185
+ setFinalCursor(0);
1186
+ try {
1187
+ const activeKeys = new Set(
1188
+ sourceOpts.filter((s) => s.enabled).map((s) => s.key)
1189
+ );
1190
+ const allEnabled = sourceOpts.every((s) => s.enabled);
1191
+ const result = await session2.search(query, allEnabled ? void 0 : activeKeys, usePruner, useExpander);
1192
+ setPipeline(result);
1193
+ setState("done");
1194
+ setStateMsg("");
1195
+ setFocus("raw");
1196
+ } catch (err) {
1197
+ setState("error");
1198
+ setErrorMsg(err instanceof Error ? err.message : String(err));
1199
+ }
1200
+ }, [query, sourceOpts, usePruner, useExpander]);
1201
+ useInput((input, key) => {
1202
+ if (key.escape) {
1203
+ if (focus === "fullpreview") {
1204
+ setFocus("final");
1205
+ return;
1206
+ }
1207
+ if (focus !== "input" && pipeline) {
1208
+ setFocus("input");
1209
+ return;
1210
+ }
1211
+ onBack();
1212
+ return;
1213
+ }
1214
+ if (focus === "input") {
1215
+ if (key.return && query.trim()) {
1216
+ doSearch();
1217
+ return;
1218
+ }
1219
+ if (key.backspace || key.delete) {
1220
+ setQuery((q) => q.slice(0, -1));
1221
+ return;
1222
+ }
1223
+ if (key.tab) {
1224
+ setFocus("sources");
1225
+ return;
1226
+ }
1227
+ if (input && input.length === 1 && !key.ctrl && !key.meta) {
1228
+ setQuery((q) => q + input);
1229
+ }
1230
+ return;
1231
+ }
1232
+ if (focus === "sources") {
1233
+ const totalOpts = sourceOpts.length + 2;
1234
+ if (key.rightArrow) setSourceCursor((c) => Math.min(c + 1, totalOpts - 1));
1235
+ if (key.leftArrow) setSourceCursor((c) => Math.max(c - 1, 0));
1236
+ if (input === " ") {
1237
+ if (sourceCursor < sourceOpts.length) {
1238
+ toggleSource(sourceOpts[sourceCursor].key);
1239
+ } else if (sourceCursor === sourceOpts.length) {
1240
+ setUsePruner((p) => !p);
1241
+ } else {
1242
+ setUseExpander((e) => !e);
1243
+ }
1244
+ return;
1245
+ }
1246
+ if (key.upArrow && sourceCursor < sourceOpts.length) {
1247
+ setSourceOpts((prev) => {
1248
+ const next = prev.map((s) => ({ ...s }));
1249
+ const target = next[sourceCursor];
1250
+ if (target) target.k = Math.min(target.k + 5, 50);
1251
+ return next;
1252
+ });
1253
+ return;
1254
+ }
1255
+ if (key.downArrow && sourceCursor < sourceOpts.length) {
1256
+ setSourceOpts((prev) => {
1257
+ const next = prev.map((s) => ({ ...s }));
1258
+ const target = next[sourceCursor];
1259
+ if (target) target.k = Math.max(target.k - 5, 5);
1260
+ return next;
1261
+ });
1262
+ return;
1263
+ }
1264
+ if (key.tab) {
1265
+ setFocus(pipeline ? "raw" : "input");
1266
+ return;
1267
+ }
1268
+ if (key.return && query.trim()) {
1269
+ doSearch();
1270
+ return;
1271
+ }
1272
+ return;
1273
+ }
1274
+ if (input === "p" && focus !== "input") {
1275
+ if (focus === "fullpreview") {
1276
+ setFocus("final");
1277
+ } else {
1278
+ setFocus("fullpreview");
1279
+ setPreviewScroll(0);
1280
+ }
1281
+ return;
1282
+ }
1283
+ if (key.tab) {
1284
+ const order = ["raw", "final", "preview", "input"];
1285
+ const idx = order.indexOf(focus);
1286
+ setFocus(order[(idx + 1) % order.length]);
1287
+ return;
1288
+ }
1289
+ if (focus === "raw") {
1290
+ if (key.downArrow) setRawCursor((c) => Math.min(c + 1, (pipeline?.raw.length ?? 1) - 1));
1291
+ if (key.upArrow) setRawCursor((c) => Math.max(c - 1, 0));
1292
+ if (input === "}") setRawCursor((c) => Math.min(c + 10, (pipeline?.raw.length ?? 1) - 1));
1293
+ if (input === "{") setRawCursor((c) => Math.max(c - 10, 0));
1294
+ if (input === "h" || key.leftArrow) setFocus("sources");
1295
+ if (input === "l" || key.rightArrow) setFocus("final");
1296
+ if (key.return) {
1297
+ setFocus("preview");
1298
+ setPreviewScroll(0);
1299
+ }
1300
+ return;
1301
+ }
1302
+ if (focus === "final") {
1303
+ const combined = pipeline ? [...pipeline.pruned, ...pipeline.expanded] : [];
1304
+ if (key.downArrow) setFinalCursor((c) => Math.min(c + 1, combined.length - 1));
1305
+ if (key.upArrow) setFinalCursor((c) => Math.max(c - 1, 0));
1306
+ if (input === "}") setFinalCursor((c) => Math.min(c + 10, combined.length - 1));
1307
+ if (input === "{") setFinalCursor((c) => Math.max(c - 10, 0));
1308
+ if (input === "h" || key.leftArrow) setFocus("raw");
1309
+ if (input === "l" || key.rightArrow) setFocus("preview");
1310
+ if (key.return) {
1311
+ setFocus("preview");
1312
+ setPreviewScroll(0);
1313
+ }
1314
+ return;
1315
+ }
1316
+ if (focus === "preview") {
1317
+ if (key.downArrow) setPreviewScroll((s) => Math.min(s + 1, maxPreviewScroll));
1318
+ if (key.upArrow) setPreviewScroll((s) => Math.max(s - 1, 0));
1319
+ if (input === "}") setPreviewScroll((s) => Math.min(s + 10, maxPreviewScroll));
1320
+ if (input === "{") setPreviewScroll((s) => Math.max(s - 10, 0));
1321
+ if (input === "d") setPreviewScroll((s) => Math.min(s + 15, maxPreviewScroll));
1322
+ if (input === "u") setPreviewScroll((s) => Math.max(s - 15, 0));
1323
+ if (input === "h" || key.leftArrow) setFocus("final");
1324
+ return;
1325
+ }
1326
+ if (focus === "fullpreview") {
1327
+ if (key.downArrow) setPreviewScroll((s) => Math.min(s + 1, maxPreviewScroll));
1328
+ if (key.upArrow) setPreviewScroll((s) => Math.max(s - 1, 0));
1329
+ if (input === "}") setPreviewScroll((s) => Math.min(s + 10, maxPreviewScroll));
1330
+ if (input === "{") setPreviewScroll((s) => Math.max(s - 10, 0));
1331
+ if (input === "d") setPreviewScroll((s) => Math.min(s + 15, maxPreviewScroll));
1332
+ if (input === "u") setPreviewScroll((s) => Math.max(s - 15, 0));
1333
+ return;
1334
+ }
1335
+ });
1336
+ const combinedResults = useMemo(() => {
1337
+ if (!pipeline) return [];
1338
+ return [
1339
+ ...pipeline.pruned.map((r) => ({ r, type: "kept" })),
1340
+ ...pipeline.expanded.map((r) => ({ r, type: "expanded" }))
1341
+ ];
1342
+ }, [pipeline]);
1343
+ const droppedPaths = useMemo(() => {
1344
+ if (!pipeline) return /* @__PURE__ */ new Set();
1345
+ return new Set(pipeline.dropped.map((r) => r.filePath ?? ""));
1346
+ }, [pipeline]);
1347
+ const rawScrollOff = centerScroll(rawCursor, pipeline?.raw.length ?? 0, listH);
1348
+ const finalScrollOff = centerScroll(finalCursor, combinedResults.length, listH);
1349
+ const fullContextLines = useMemo(() => {
1350
+ if (!pipeline) return [];
1351
+ const final = [...pipeline.pruned, ...pipeline.expanded];
1352
+ const lines = [];
1353
+ for (const r of final) {
1354
+ const info = resultLabel(r);
1355
+ lines.push(`\u2501\u2501\u2501 ${info.path} L${info.line} \u2501\u2501\u2501`);
1356
+ lines.push(...r.content.split("\n"));
1357
+ lines.push("");
1358
+ }
1359
+ return lines;
1360
+ }, [pipeline]);
1361
+ const currentPreviewLines = focus === "fullpreview" ? fullContextLines : previewLines;
1362
+ const currentMaxScroll = Math.max(0, currentPreviewLines.length - previewH);
1363
+ const visiblePreview = currentPreviewLines.slice(previewScroll, previewScroll + previewH);
1364
+ const scrollPct = currentMaxScroll > 0 ? Math.round(previewScroll / currentMaxScroll * 100) : 100;
1365
+ useEffect(() => {
1366
+ setPreviewScroll((s) => Math.min(s, currentMaxScroll));
1367
+ }, [currentMaxScroll]);
1368
+ const spinnerFrames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
1369
+ const [spinIdx, setSpinIdx] = useState(0);
1370
+ useEffect(() => {
1371
+ if (state !== "initializing") return;
1372
+ const timer = setInterval(() => setSpinIdx((i) => (i + 1) % spinnerFrames.length), 80);
1373
+ return () => clearInterval(timer);
1374
+ }, [state]);
1375
+ if (state === "initializing") {
1376
+ return /* @__PURE__ */ jsxs(
1377
+ Box,
1378
+ {
1379
+ flexDirection: "column",
1380
+ width,
1381
+ height: height - 4,
1382
+ justifyContent: "center",
1383
+ alignItems: "center",
1384
+ children: [
1385
+ /* @__PURE__ */ jsxs(
1386
+ Box,
1387
+ {
1388
+ flexDirection: "column",
1389
+ alignItems: "center",
1390
+ borderStyle: "round",
1391
+ borderColor: C.border,
1392
+ paddingX: 6,
1393
+ paddingY: 2,
1394
+ children: [
1395
+ /* @__PURE__ */ jsxs(Text, { color: C.aurora, bold: true, children: [
1396
+ spinnerFrames[spinIdx],
1397
+ " Loading Search Engine"
1398
+ ] }),
1399
+ /* @__PURE__ */ jsx(Text, { color: C.dim, children: " " }),
1400
+ /* @__PURE__ */ jsx(Text, { color: C.dim, children: "Initializing HNSW indices and plugins..." }),
1401
+ /* @__PURE__ */ jsx(Text, { color: C.dim, children: "This only happens once per session" })
1402
+ ]
1403
+ }
1404
+ ),
1405
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { color: C.dim, children: "Esc to go back" }) })
1406
+ ]
1407
+ }
1408
+ );
1409
+ }
1410
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", width, height: height - 4, children: [
1411
+ /* @__PURE__ */ jsxs(Box, { paddingX: 1, flexDirection: "column", marginBottom: 1, children: [
1412
+ /* @__PURE__ */ jsxs(Box, { children: [
1413
+ /* @__PURE__ */ jsx(Text, { color: C.aurora, bold: true, children: "\u{1F50D} " }),
1414
+ /* @__PURE__ */ jsxs(Text, { color: focus === "input" ? C.text : C.dim, children: [
1415
+ query,
1416
+ /* @__PURE__ */ jsx(Text, { color: focus === "input" ? C.aurora : C.dim, children: "\u258E" })
1417
+ ] }),
1418
+ /* @__PURE__ */ jsxs(Text, { color: C.dim, children: [
1419
+ " ",
1420
+ state === "initializing" ? "\u27F3 " + stateMsg : state === "searching" ? "\u27F3 Searching..." : state === "error" ? "\u2717 " + errorMsg : state === "done" ? `${pipeline?.raw.length ?? 0} results` : "Enter to search"
1421
+ ] })
1422
+ ] }),
1423
+ /* @__PURE__ */ jsxs(Box, { marginTop: 1, children: [
1424
+ /* @__PURE__ */ jsx(Text, { color: focus === "sources" ? C.aurora : C.dim, children: "Sources: " }),
1425
+ sourceOpts.map((s, i) => {
1426
+ const isFocused = focus === "sources" && i === sourceCursor;
1427
+ return /* @__PURE__ */ jsxs(Text, { children: [
1428
+ /* @__PURE__ */ jsxs(
1429
+ Text,
1430
+ {
1431
+ color: isFocused ? C.text : s.enabled ? C.aurora : C.dim,
1432
+ bold: isFocused,
1433
+ underline: isFocused,
1434
+ children: [
1435
+ "[",
1436
+ s.enabled ? "\u25A0" : "\u25A1",
1437
+ "] ",
1438
+ s.label
1439
+ ]
1440
+ }
1441
+ ),
1442
+ /* @__PURE__ */ jsxs(Text, { color: isFocused ? C.cyan : C.dim, children: [
1443
+ ":",
1444
+ s.k
1445
+ ] }),
1446
+ /* @__PURE__ */ jsx(Text, { color: C.dim, children: " " })
1447
+ ] }, s.key);
1448
+ }),
1449
+ /* @__PURE__ */ jsx(Text, { color: C.dim, children: "\u2502 " }),
1450
+ /* @__PURE__ */ jsxs(
1451
+ Text,
1452
+ {
1453
+ color: focus === "sources" && sourceCursor === sourceOpts.length ? C.text : usePruner ? C.purple : C.dim,
1454
+ bold: focus === "sources" && sourceCursor === sourceOpts.length,
1455
+ underline: focus === "sources" && sourceCursor === sourceOpts.length,
1456
+ children: [
1457
+ "[",
1458
+ usePruner ? "\u25A0" : "\u25A1",
1459
+ "] Pruner"
1460
+ ]
1461
+ }
1462
+ ),
1463
+ /* @__PURE__ */ jsx(Text, { color: C.dim, children: " " }),
1464
+ /* @__PURE__ */ jsxs(
1465
+ Text,
1466
+ {
1467
+ color: focus === "sources" && sourceCursor === sourceOpts.length + 1 ? C.text : useExpander ? C.purple : C.dim,
1468
+ bold: focus === "sources" && sourceCursor === sourceOpts.length + 1,
1469
+ underline: focus === "sources" && sourceCursor === sourceOpts.length + 1,
1470
+ children: [
1471
+ "[",
1472
+ useExpander ? "\u25A0" : "\u25A1",
1473
+ "] Expander"
1474
+ ]
1475
+ }
1476
+ ),
1477
+ /* @__PURE__ */ jsx(Text, { color: C.dim, children: " (\u2190\u2192 move, Space toggle, \u2191\u2193 adjust K)" })
1478
+ ] })
1479
+ ] }),
1480
+ focus === "fullpreview" ? /* @__PURE__ */ jsxs(Box, { flexDirection: "column", width, height: listH + 2, marginTop: 1, paddingX: 1, children: [
1481
+ /* @__PURE__ */ jsxs(Box, { marginBottom: 0, justifyContent: "space-between", children: [
1482
+ /* @__PURE__ */ jsx(Text, { color: C.aurora, bold: true, children: "Full Context Preview" }),
1483
+ /* @__PURE__ */ jsxs(Text, { children: [
1484
+ /* @__PURE__ */ jsx(Text, { color: C.dim, italic: true, children: "What the agent sees " }),
1485
+ /* @__PURE__ */ jsxs(Text, { color: C.cyan, children: [
1486
+ scrollPct,
1487
+ "%"
1488
+ ] }),
1489
+ /* @__PURE__ */ jsx(Text, { color: C.dim, children: " P to close" })
1490
+ ] })
1491
+ ] }),
1492
+ /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: visiblePreview.map((line, i) => {
1493
+ const isHeader = line.startsWith("\u2501\u2501\u2501");
1494
+ if (isHeader) {
1495
+ return /* @__PURE__ */ jsx(Box, { height: 1, children: /* @__PURE__ */ jsx(Text, { color: C.purple, bold: true, wrap: "truncate", children: truncate(line, width - 4) }) }, previewScroll + i);
1496
+ }
1497
+ const segs = highlightLine(line, width - 8);
1498
+ return /* @__PURE__ */ jsx(Box, { height: 1, children: /* @__PURE__ */ jsxs(Text, { wrap: "truncate", children: [
1499
+ /* @__PURE__ */ jsxs(Text, { color: C.dim, children: [
1500
+ String(previewScroll + i + 1).padStart(4),
1501
+ "\u2502"
1502
+ ] }),
1503
+ /* @__PURE__ */ jsx(HighlightedLine, { segments: segs })
1504
+ ] }) }, previewScroll + i);
1505
+ }) })
1506
+ ] }) : (
1507
+ /* Two result columns + wide preview */
1508
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", width, height: listH, marginTop: 1, children: [
1509
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "column", width: rawW, paddingX: 1, children: [
1510
+ /* @__PURE__ */ jsx(Box, { marginBottom: 0, children: /* @__PURE__ */ jsxs(Text, { color: focus === "raw" ? C.aurora : C.dim, bold: true, children: [
1511
+ "Raw (",
1512
+ pipeline?.raw.length ?? 0,
1513
+ ")"
1514
+ ] }) }),
1515
+ pipeline && pipeline.raw.slice(rawScrollOff, rawScrollOff + listH - 1).map((r, vi) => {
1516
+ const idx = rawScrollOff + vi;
1517
+ const isCursor = focus === "raw" && idx === rawCursor;
1518
+ const info = resultLabel(r);
1519
+ const isDimmed = droppedPaths.has(r.filePath ?? "");
1520
+ const ptr = isCursor ? "\u25B8" : " ";
1521
+ const scorePct = Math.round(info.score * 100);
1522
+ return /* @__PURE__ */ jsx(Box, { height: 1, children: /* @__PURE__ */ jsxs(Text, { wrap: "truncate", children: [
1523
+ /* @__PURE__ */ jsxs(Text, { color: isCursor ? C.aurora : C.dim, children: [
1524
+ ptr,
1525
+ " "
1526
+ ] }),
1527
+ /* @__PURE__ */ jsx(
1528
+ Text,
1529
+ {
1530
+ color: isDimmed ? C.error : isCursor ? C.text : C.dim,
1531
+ strikethrough: isDimmed,
1532
+ children: truncate(info.name, rawW - 10)
1533
+ }
1534
+ ),
1535
+ /* @__PURE__ */ jsxs(Text, { color: isDimmed ? C.error : C.dim, children: [
1536
+ " ",
1537
+ scorePct,
1538
+ "%"
1539
+ ] })
1540
+ ] }) }, idx);
1541
+ })
1542
+ ] }),
1543
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "column", width: prunedW, paddingX: 1, children: [
1544
+ /* @__PURE__ */ jsxs(Box, { marginBottom: 0, children: [
1545
+ /* @__PURE__ */ jsxs(Text, { color: focus === "final" ? C.aurora : C.dim, bold: true, children: [
1546
+ "Final (",
1547
+ combinedResults.length,
1548
+ ")"
1549
+ ] }),
1550
+ (pipeline?.expanded.length ?? 0) > 0 && /* @__PURE__ */ jsxs(Text, { color: C.success, children: [
1551
+ " +",
1552
+ pipeline?.expanded.length,
1553
+ "\u25C6"
1554
+ ] }),
1555
+ pipeline && pipeline.dropped.length > 0 && /* @__PURE__ */ jsxs(Text, { color: C.error, children: [
1556
+ " -",
1557
+ pipeline.dropped.length
1558
+ ] })
1559
+ ] }),
1560
+ combinedResults.slice(finalScrollOff, finalScrollOff + listH - 1).map((item, vi) => {
1561
+ const idx = finalScrollOff + vi;
1562
+ const isCursor = focus === "final" && idx === finalCursor;
1563
+ const info = resultLabel(item.r);
1564
+ const isExpanded = item.type === "expanded";
1565
+ const ptr = isCursor ? "\u25B8" : isExpanded ? "\u25C6" : " ";
1566
+ const shortFile = info.path.split("/").pop() ?? info.path;
1567
+ return /* @__PURE__ */ jsx(Box, { height: 1, children: /* @__PURE__ */ jsxs(Text, { wrap: "truncate", children: [
1568
+ /* @__PURE__ */ jsx(Text, { color: isCursor ? C.aurora : isExpanded ? C.success : C.dim, children: ptr }),
1569
+ /* @__PURE__ */ jsxs(Text, { color: C.dim, children: [
1570
+ String(idx + 1).padStart(2),
1571
+ " "
1572
+ ] }),
1573
+ /* @__PURE__ */ jsx(Text, { color: isCursor ? C.text : isExpanded ? C.success : C.dim, children: truncate(shortFile, prunedW - 8) })
1574
+ ] }) }, idx);
1575
+ })
1576
+ ] }),
1577
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "column", width: previewW, paddingX: 1, children: [
1578
+ /* @__PURE__ */ jsxs(Box, { marginBottom: 0, justifyContent: "space-between", children: [
1579
+ /* @__PURE__ */ jsx(Text, { color: focus === "preview" ? C.aurora : C.dim, bold: true, children: "Preview" }),
1580
+ /* @__PURE__ */ jsxs(Text, { children: [
1581
+ focus !== "preview" && /* @__PURE__ */ jsx(Text, { color: C.dim, italic: true, children: "Enter/\u2192 to scroll " }),
1582
+ currentPreviewLines.length > previewH && /* @__PURE__ */ jsxs(Text, { color: focus === "preview" ? C.cyan : C.dim, children: [
1583
+ scrollPct,
1584
+ "%"
1585
+ ] })
1586
+ ] })
1587
+ ] }),
1588
+ activeResult && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
1589
+ /* @__PURE__ */ jsxs(Text, { color: C.dim, wrap: "truncate", children: [
1590
+ truncate(resultLabel(activeResult).path, previewW - 4),
1591
+ " L",
1592
+ resultLabel(activeResult).line
1593
+ ] }),
1594
+ visiblePreview.map((line, i) => {
1595
+ const segs = highlightLine(line, previewW - 5);
1596
+ return /* @__PURE__ */ jsx(Box, { height: 1, children: /* @__PURE__ */ jsxs(Text, { wrap: "truncate", children: [
1597
+ /* @__PURE__ */ jsxs(Text, { color: C.dim, children: [
1598
+ String(previewScroll + i + 1).padStart(3),
1599
+ "\u2502"
1600
+ ] }),
1601
+ /* @__PURE__ */ jsx(HighlightedLine, { segments: segs })
1602
+ ] }) }, previewScroll + i);
1603
+ })
1604
+ ] })
1605
+ ] })
1606
+ ] })
1607
+ ),
1608
+ pipeline && /* @__PURE__ */ jsx(Box, { paddingX: 1, marginTop: 0, children: /* @__PURE__ */ jsxs(Text, { color: C.dim, children: [
1609
+ "\u23F1 search: ",
1610
+ pipeline.timings.search,
1611
+ "ms",
1612
+ pipeline.prunerName && /* @__PURE__ */ jsxs(Text, { children: [
1613
+ " ",
1614
+ "prune: ",
1615
+ pipeline.timings.prune,
1616
+ "ms (",
1617
+ pipeline.prunerName,
1618
+ ", -",
1619
+ pipeline.dropped.length,
1620
+ ")"
1621
+ ] }),
1622
+ pipeline.expanderName && pipeline.expanded.length > 0 && /* @__PURE__ */ jsxs(Text, { children: [
1623
+ " ",
1624
+ "expand: ",
1625
+ pipeline.timings.expand,
1626
+ "ms (",
1627
+ pipeline.expanderName,
1628
+ ", +",
1629
+ pipeline.expanded.length,
1630
+ ")"
1631
+ ] }),
1632
+ " ",
1633
+ "total: ",
1634
+ pipeline.timings.total,
1635
+ "ms"
1636
+ ] }) })
1637
+ ] });
1638
+ }
1639
+ __name(SemanticSearchView, "SemanticSearchView");
1640
+ function CallGraphView({ dbPath, width, height, onBack }) {
1641
+ const [searchQuery, setSearchQuery] = useState("");
1642
+ const [rootNodes, setRootNodes] = useState([]);
1643
+ const [cursor, setCursor] = useState(0);
1644
+ const flatNodes = useMemo(() => {
1645
+ const flat = [];
1646
+ function walk(nodes, d) {
1647
+ for (const n of nodes) {
1648
+ flat.push({ node: n, depth: d });
1649
+ walk(n.children, d + 1);
1650
+ }
1651
+ }
1652
+ __name(walk, "walk");
1653
+ walk(rootNodes, 0);
1654
+ return flat;
1655
+ }, [rootNodes]);
1656
+ useInput((input, key) => {
1657
+ if (key.escape) {
1658
+ if (rootNodes.length > 0) {
1659
+ setRootNodes([]);
1660
+ setSearchQuery("");
1661
+ } else onBack();
1662
+ }
1663
+ if (key.downArrow) setCursor((c) => Math.min(c + 1, flatNodes.length - 1));
1664
+ if (key.upArrow) setCursor((c) => Math.max(c - 1, 0));
1665
+ if (key.return && flatNodes[cursor]) {
1666
+ const tree = fetchCallTree(dbPath, flatNodes[cursor].node.chunkId, 3);
1667
+ if (tree.children.length > 0) {
1668
+ setRootNodes([tree]);
1669
+ setCursor(0);
1670
+ }
1671
+ }
1672
+ if (key.backspace || key.delete) {
1673
+ setSearchQuery((q) => q.slice(0, -1));
1674
+ }
1675
+ if (input && input.length === 1 && !key.ctrl && !key.meta) {
1676
+ setSearchQuery((q) => q + input);
1677
+ }
1678
+ });
1679
+ useEffect(() => {
1680
+ if (searchQuery.length < 2) {
1681
+ setRootNodes([]);
1682
+ return;
1683
+ }
1684
+ try {
1685
+ const rows = searchSymbols(dbPath, searchQuery, 10);
1686
+ const trees = rows.map((r) => fetchCallTree(dbPath, r.id, 2));
1687
+ setRootNodes(trees);
1688
+ setCursor(0);
1689
+ } catch {
1690
+ }
1691
+ }, [searchQuery, dbPath]);
1692
+ const listH = Math.max(5, height - 8);
1693
+ const scrollOff = centerScroll(cursor, flatNodes.length, listH);
1694
+ const visible = flatNodes.slice(scrollOff, scrollOff + listH);
1695
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", width, height: height - 4, paddingX: 1, children: [
1696
+ /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(Text, { color: C.aurora, bold: true, children: "Call Graph" }) }),
1697
+ /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
1698
+ /* @__PURE__ */ jsx(Text, { color: C.dim, children: "\u{1F50D} Search: " }),
1699
+ /* @__PURE__ */ jsx(Text, { color: C.text, children: searchQuery || "(type to search symbols\u2026)" })
1700
+ ] }),
1701
+ flatNodes.length === 0 && searchQuery.length >= 2 && /* @__PURE__ */ jsx(Text, { color: C.dim, children: "No matching symbols found." }),
1702
+ visible.map((item, vi) => {
1703
+ const idx = scrollOff + vi;
1704
+ const isCursor = idx === cursor;
1705
+ const indent = " ".repeat(item.depth);
1706
+ const prefix = item.depth > 0 ? "\u251C\u2500\u2500 " : "";
1707
+ return /* @__PURE__ */ jsx(Box, { height: 1, children: /* @__PURE__ */ jsxs(Text, { wrap: "truncate", children: [
1708
+ /* @__PURE__ */ jsx(Text, { color: isCursor ? C.aurora : C.dim, children: isCursor ? "\u25B8 " : " " }),
1709
+ /* @__PURE__ */ jsxs(Text, { color: C.dim, children: [
1710
+ indent,
1711
+ prefix
1712
+ ] }),
1713
+ /* @__PURE__ */ jsx(Text, { color: isCursor ? C.aurora : C.purple, bold: isCursor, children: item.node.symbol }),
1714
+ /* @__PURE__ */ jsx(Text, { color: C.dim, children: " " }),
1715
+ /* @__PURE__ */ jsx(Text, { color: C.dim, children: truncate(item.node.filePath, width - indent.length * 2 - item.node.symbol.length - 12) }),
1716
+ item.node.children.length > 0 && /* @__PURE__ */ jsxs(Text, { color: C.dim, children: [
1717
+ " (",
1718
+ item.node.children.length,
1719
+ ")"
1720
+ ] })
1721
+ ] }) }, `${item.node.chunkId}-${vi}`);
1722
+ })
1723
+ ] });
1724
+ }
1725
+ __name(CallGraphView, "CallGraphView");
1726
+ function Header({ repoPath, dbSizeMB, view, breadcrumb, width }) {
1727
+ const crumb = breadcrumb.length > 0 ? " \u25B8 " + breadcrumb.join(" \u25B8 ") : "";
1728
+ const right = `${repoPath} ${dbSizeMB}MB`;
1729
+ return /* @__PURE__ */ jsxs(Box, { width, height: 2, paddingX: 1, flexDirection: "column", children: [
1730
+ /* @__PURE__ */ jsxs(Box, { justifyContent: "space-between", children: [
1731
+ /* @__PURE__ */ jsxs(Text, { children: [
1732
+ /* @__PURE__ */ jsx(Text, { color: C.aurora, bold: true, children: "\u26A1 BrainBank Explorer" }),
1733
+ /* @__PURE__ */ jsx(Text, { color: C.dim, children: crumb })
1734
+ ] }),
1735
+ /* @__PURE__ */ jsx(Text, { color: C.dim, children: truncate(right, Math.max(10, width - 40)) })
1736
+ ] }),
1737
+ /* @__PURE__ */ jsx(Text, { color: C.border, children: "\u2500".repeat(width - 2) })
1738
+ ] });
1739
+ }
1740
+ __name(Header, "Header");
1741
+ function Footer({ view, width }) {
1742
+ const hints = {
1743
+ dashboard: "\u2191\u2193 navigate Enter drill in / search g call graph q quit",
1744
+ files: "\u2191\u2193 navigate Enter view chunks s sort / filter Esc back q quit",
1745
+ chunks: "Tab focus \u2191\u2193 scroll {/} jump 10 u/d page Enter preview Esc back",
1746
+ callgraph: "type to search \u2191\u2193 navigate Enter expand Esc back q quit",
1747
+ search: "type query \u2192 Enter \u2190\u2192 panels {/} jump 10 P full context Tab sources Space toggle Esc back"
1748
+ };
1749
+ return /* @__PURE__ */ jsxs(Box, { width, height: 2, paddingX: 1, flexDirection: "column", children: [
1750
+ /* @__PURE__ */ jsx(Text, { color: C.border, children: "\u2500".repeat(width - 2) }),
1751
+ /* @__PURE__ */ jsx(Text, { color: C.dim, children: hints[view].split(/(\S+:)/).map(
1752
+ (part, i) => part.endsWith(":") ? /* @__PURE__ */ jsxs(Text, { color: C.aurora, children: [
1753
+ part,
1754
+ " "
1755
+ ] }, i) : /* @__PURE__ */ jsx(Text, { children: part }, i)
1756
+ ) })
1757
+ ] });
1758
+ }
1759
+ __name(Footer, "Footer");
1760
+ function StatsApp({ dbPath, repoPath, configPath }) {
1761
+ const { exit } = useApp();
1762
+ const { stdout } = useStdout();
1763
+ const [rawW, setRawW] = useState(stdout?.columns || 100);
1764
+ const [rawH, setRawH] = useState(stdout?.rows || 30);
1765
+ const [view, setView] = useState("dashboard");
1766
+ const [breadcrumb, setBreadcrumb] = useState([]);
1767
+ const [currentDir, setCurrentDir] = useState("");
1768
+ const [currentFile, setCurrentFile] = useState("");
1769
+ const searchSessionRef = useRef(null);
1770
+ const getSearchSession = useCallback(() => {
1771
+ if (!searchSessionRef.current) {
1772
+ searchSessionRef.current = new BrainSearchSession(repoPath);
1773
+ }
1774
+ return searchSessionRef.current;
1775
+ }, [repoPath]);
1776
+ useEffect(() => {
1777
+ return () => {
1778
+ searchSessionRef.current?.close();
1779
+ };
1780
+ }, []);
1781
+ const width = Math.floor(rawW * 0.9);
1782
+ const height = Math.floor(rawH * 0.9);
1783
+ useEffect(() => {
1784
+ if (!stdout) return;
1785
+ const onResize = /* @__PURE__ */ __name(() => {
1786
+ setRawW(stdout.columns);
1787
+ setRawH(stdout.rows);
1788
+ }, "onResize");
1789
+ stdout.on("resize", onResize);
1790
+ return () => {
1791
+ stdout.off("resize", onResize);
1792
+ };
1793
+ }, [stdout]);
1794
+ useInput((input) => {
1795
+ if (input === "q" && view !== "search" && view !== "callgraph") {
1796
+ exit();
1797
+ }
1798
+ });
1799
+ const overview = useMemo(() => fetchOverview(dbPath, repoPath, configPath), [dbPath, repoPath, configPath]);
1800
+ const languages = useMemo(() => fetchLanguageBreakdown(dbPath), [dbPath]);
1801
+ const dirs = useMemo(() => fetchDirectories(dbPath), [dbPath]);
1802
+ const drillDir = /* @__PURE__ */ __name((dir) => {
1803
+ setCurrentDir(dir);
1804
+ setBreadcrumb([dir + "/"]);
1805
+ setView("files");
1806
+ }, "drillDir");
1807
+ const drillFile = /* @__PURE__ */ __name((filePath) => {
1808
+ setCurrentFile(filePath);
1809
+ const fileName = filePath.split("/").pop() || filePath;
1810
+ setBreadcrumb([currentDir + "/", fileName]);
1811
+ setView("chunks");
1812
+ }, "drillFile");
1813
+ const goBack = /* @__PURE__ */ __name(() => {
1814
+ if (view === "chunks") {
1815
+ setBreadcrumb([currentDir + "/"]);
1816
+ setView("files");
1817
+ } else if (view === "files") {
1818
+ setBreadcrumb([]);
1819
+ setView("dashboard");
1820
+ } else if (view === "callgraph" || view === "search") {
1821
+ setBreadcrumb([]);
1822
+ setView("dashboard");
1823
+ }
1824
+ }, "goBack");
1825
+ const goCallGraph = /* @__PURE__ */ __name(() => {
1826
+ setBreadcrumb(["Call Graph"]);
1827
+ setView("callgraph");
1828
+ }, "goCallGraph");
1829
+ const goSearch = /* @__PURE__ */ __name(() => {
1830
+ setBreadcrumb(["Search"]);
1831
+ setView("search");
1832
+ }, "goSearch");
1833
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", width, height, children: [
1834
+ /* @__PURE__ */ jsx(Header, { repoPath: overview.repoPath, dbSizeMB: overview.dbSizeMB, view, breadcrumb, width }),
1835
+ view === "dashboard" && /* @__PURE__ */ jsx(
1836
+ DashboardView,
1837
+ {
1838
+ overview,
1839
+ languages,
1840
+ dirs,
1841
+ width,
1842
+ height,
1843
+ onDrillDir: drillDir,
1844
+ onCallGraph: goCallGraph,
1845
+ onSearch: goSearch
1846
+ }
1847
+ ),
1848
+ view === "files" && /* @__PURE__ */ jsx(
1849
+ FileExplorerView,
1850
+ {
1851
+ dbPath,
1852
+ dir: currentDir,
1853
+ width,
1854
+ height,
1855
+ onDrillFile: drillFile,
1856
+ onBack: goBack
1857
+ }
1858
+ ),
1859
+ view === "chunks" && /* @__PURE__ */ jsx(
1860
+ ChunkViewerView,
1861
+ {
1862
+ dbPath,
1863
+ filePath: currentFile,
1864
+ width,
1865
+ height,
1866
+ onBack: goBack
1867
+ }
1868
+ ),
1869
+ view === "callgraph" && /* @__PURE__ */ jsx(
1870
+ CallGraphView,
1871
+ {
1872
+ dbPath,
1873
+ width,
1874
+ height,
1875
+ onBack: goBack
1876
+ }
1877
+ ),
1878
+ view === "search" && /* @__PURE__ */ jsx(
1879
+ SemanticSearchView,
1880
+ {
1881
+ repoPath,
1882
+ width,
1883
+ height,
1884
+ onBack: goBack,
1885
+ session: getSearchSession()
1886
+ }
1887
+ ),
1888
+ /* @__PURE__ */ jsx(Footer, { view, width })
1889
+ ] });
1890
+ }
1891
+ __name(StatsApp, "StatsApp");
1892
+ async function runStatsTui(dbPath, repoPath, configPath) {
1893
+ process.stdout.write("\x1B[2J\x1B[H\x1B[?25l");
1894
+ const instance = render(
1895
+ /* @__PURE__ */ jsx(StatsApp, { dbPath, repoPath, configPath })
1896
+ );
1897
+ await instance.waitUntilExit();
1898
+ process.stdout.write("\x1B[?25h\x1B[2J\x1B[H");
1899
+ }
1900
+ __name(runStatsTui, "runStatsTui");
1901
+ export {
1902
+ runStatsTui
1903
+ };
1904
+ //# sourceMappingURL=stats-tui-ZY2NQSEA.js.map