glancey 2.0.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.
Files changed (336) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +435 -0
  3. package/dist/__tests__/ast-chunker.test.d.ts +2 -0
  4. package/dist/__tests__/ast-chunker.test.d.ts.map +1 -0
  5. package/dist/__tests__/ast-chunker.test.js +307 -0
  6. package/dist/__tests__/ast-chunker.test.js.map +1 -0
  7. package/dist/__tests__/config.test.d.ts +2 -0
  8. package/dist/__tests__/config.test.d.ts.map +1 -0
  9. package/dist/__tests__/config.test.js +242 -0
  10. package/dist/__tests__/config.test.js.map +1 -0
  11. package/dist/__tests__/dashboard/beads.test.d.ts +2 -0
  12. package/dist/__tests__/dashboard/beads.test.d.ts.map +1 -0
  13. package/dist/__tests__/dashboard/beads.test.js +151 -0
  14. package/dist/__tests__/dashboard/beads.test.js.map +1 -0
  15. package/dist/__tests__/dashboard/index.test.d.ts +2 -0
  16. package/dist/__tests__/dashboard/index.test.d.ts.map +1 -0
  17. package/dist/__tests__/dashboard/index.test.js +116 -0
  18. package/dist/__tests__/dashboard/index.test.js.map +1 -0
  19. package/dist/__tests__/dashboard/routes.test.d.ts +2 -0
  20. package/dist/__tests__/dashboard/routes.test.d.ts.map +1 -0
  21. package/dist/__tests__/dashboard/routes.test.js +125 -0
  22. package/dist/__tests__/dashboard/routes.test.js.map +1 -0
  23. package/dist/__tests__/dashboard/server.test.d.ts +2 -0
  24. package/dist/__tests__/dashboard/server.test.d.ts.map +1 -0
  25. package/dist/__tests__/dashboard/server.test.js +75 -0
  26. package/dist/__tests__/dashboard/server.test.js.map +1 -0
  27. package/dist/__tests__/dashboard/state.test.d.ts +2 -0
  28. package/dist/__tests__/dashboard/state.test.d.ts.map +1 -0
  29. package/dist/__tests__/dashboard/state.test.js +124 -0
  30. package/dist/__tests__/dashboard/state.test.js.map +1 -0
  31. package/dist/__tests__/dashboard/token-tracking.test.d.ts +2 -0
  32. package/dist/__tests__/dashboard/token-tracking.test.d.ts.map +1 -0
  33. package/dist/__tests__/dashboard/token-tracking.test.js +315 -0
  34. package/dist/__tests__/dashboard/token-tracking.test.js.map +1 -0
  35. package/dist/__tests__/embeddings/factory.test.d.ts +2 -0
  36. package/dist/__tests__/embeddings/factory.test.d.ts.map +1 -0
  37. package/dist/__tests__/embeddings/factory.test.js +157 -0
  38. package/dist/__tests__/embeddings/factory.test.js.map +1 -0
  39. package/dist/__tests__/embeddings/gemini.test.d.ts +2 -0
  40. package/dist/__tests__/embeddings/gemini.test.d.ts.map +1 -0
  41. package/dist/__tests__/embeddings/gemini.test.js +178 -0
  42. package/dist/__tests__/embeddings/gemini.test.js.map +1 -0
  43. package/dist/__tests__/embeddings/ollama.test.d.ts +2 -0
  44. package/dist/__tests__/embeddings/ollama.test.d.ts.map +1 -0
  45. package/dist/__tests__/embeddings/ollama.test.js +273 -0
  46. package/dist/__tests__/embeddings/ollama.test.js.map +1 -0
  47. package/dist/__tests__/embeddings/rate-limiter.test.d.ts +2 -0
  48. package/dist/__tests__/embeddings/rate-limiter.test.d.ts.map +1 -0
  49. package/dist/__tests__/embeddings/rate-limiter.test.js +163 -0
  50. package/dist/__tests__/embeddings/rate-limiter.test.js.map +1 -0
  51. package/dist/__tests__/embeddings/retry.test.d.ts +2 -0
  52. package/dist/__tests__/embeddings/retry.test.d.ts.map +1 -0
  53. package/dist/__tests__/embeddings/retry.test.js +334 -0
  54. package/dist/__tests__/embeddings/retry.test.js.map +1 -0
  55. package/dist/__tests__/embeddings/types.test.d.ts +2 -0
  56. package/dist/__tests__/embeddings/types.test.d.ts.map +1 -0
  57. package/dist/__tests__/embeddings/types.test.js +31 -0
  58. package/dist/__tests__/embeddings/types.test.js.map +1 -0
  59. package/dist/__tests__/mocks/embedding-backend.mock.d.ts +10 -0
  60. package/dist/__tests__/mocks/embedding-backend.mock.d.ts.map +1 -0
  61. package/dist/__tests__/mocks/embedding-backend.mock.js +39 -0
  62. package/dist/__tests__/mocks/embedding-backend.mock.js.map +1 -0
  63. package/dist/__tests__/mocks/fetch.mock.d.ts +49 -0
  64. package/dist/__tests__/mocks/fetch.mock.d.ts.map +1 -0
  65. package/dist/__tests__/mocks/fetch.mock.js +102 -0
  66. package/dist/__tests__/mocks/fetch.mock.js.map +1 -0
  67. package/dist/__tests__/mocks/lancedb.mock.d.ts +38 -0
  68. package/dist/__tests__/mocks/lancedb.mock.d.ts.map +1 -0
  69. package/dist/__tests__/mocks/lancedb.mock.js +63 -0
  70. package/dist/__tests__/mocks/lancedb.mock.js.map +1 -0
  71. package/dist/__tests__/search/clustering.test.d.ts +2 -0
  72. package/dist/__tests__/search/clustering.test.d.ts.map +1 -0
  73. package/dist/__tests__/search/clustering.test.js +230 -0
  74. package/dist/__tests__/search/clustering.test.js.map +1 -0
  75. package/dist/__tests__/search/hybrid-search.test.d.ts +2 -0
  76. package/dist/__tests__/search/hybrid-search.test.d.ts.map +1 -0
  77. package/dist/__tests__/search/hybrid-search.test.js +279 -0
  78. package/dist/__tests__/search/hybrid-search.test.js.map +1 -0
  79. package/dist/__tests__/search/indexer.test.d.ts +2 -0
  80. package/dist/__tests__/search/indexer.test.d.ts.map +1 -0
  81. package/dist/__tests__/search/indexer.test.js +1305 -0
  82. package/dist/__tests__/search/indexer.test.js.map +1 -0
  83. package/dist/__tests__/search/tree-sitter-chunker.test.d.ts +2 -0
  84. package/dist/__tests__/search/tree-sitter-chunker.test.d.ts.map +1 -0
  85. package/dist/__tests__/search/tree-sitter-chunker.test.js +228 -0
  86. package/dist/__tests__/search/tree-sitter-chunker.test.js.map +1 -0
  87. package/dist/__tests__/setup.d.ts +2 -0
  88. package/dist/__tests__/setup.d.ts.map +1 -0
  89. package/dist/__tests__/setup.js +11 -0
  90. package/dist/__tests__/setup.js.map +1 -0
  91. package/dist/__tests__/tools/clustering-handlers.test.d.ts +2 -0
  92. package/dist/__tests__/tools/clustering-handlers.test.d.ts.map +1 -0
  93. package/dist/__tests__/tools/clustering-handlers.test.js +286 -0
  94. package/dist/__tests__/tools/clustering-handlers.test.js.map +1 -0
  95. package/dist/__tests__/tools/commit-handlers.test.d.ts +2 -0
  96. package/dist/__tests__/tools/commit-handlers.test.d.ts.map +1 -0
  97. package/dist/__tests__/tools/commit-handlers.test.js +153 -0
  98. package/dist/__tests__/tools/commit-handlers.test.js.map +1 -0
  99. package/dist/__tests__/tools/index-handlers.test.d.ts +2 -0
  100. package/dist/__tests__/tools/index-handlers.test.d.ts.map +1 -0
  101. package/dist/__tests__/tools/index-handlers.test.js +184 -0
  102. package/dist/__tests__/tools/index-handlers.test.js.map +1 -0
  103. package/dist/__tests__/tools/instructions-handlers.test.d.ts +2 -0
  104. package/dist/__tests__/tools/instructions-handlers.test.d.ts.map +1 -0
  105. package/dist/__tests__/tools/instructions-handlers.test.js +53 -0
  106. package/dist/__tests__/tools/instructions-handlers.test.js.map +1 -0
  107. package/dist/__tests__/tools/memory-handlers.test.d.ts +2 -0
  108. package/dist/__tests__/tools/memory-handlers.test.d.ts.map +1 -0
  109. package/dist/__tests__/tools/memory-handlers.test.js +175 -0
  110. package/dist/__tests__/tools/memory-handlers.test.js.map +1 -0
  111. package/dist/__tests__/tools/search-handlers.test.d.ts +2 -0
  112. package/dist/__tests__/tools/search-handlers.test.d.ts.map +1 -0
  113. package/dist/__tests__/tools/search-handlers.test.js +229 -0
  114. package/dist/__tests__/tools/search-handlers.test.js.map +1 -0
  115. package/dist/__tests__/tools/symbol-handlers.test.d.ts +2 -0
  116. package/dist/__tests__/tools/symbol-handlers.test.d.ts.map +1 -0
  117. package/dist/__tests__/tools/symbol-handlers.test.js +348 -0
  118. package/dist/__tests__/tools/symbol-handlers.test.js.map +1 -0
  119. package/dist/__tests__/tools/worktree-handlers.test.d.ts +2 -0
  120. package/dist/__tests__/tools/worktree-handlers.test.d.ts.map +1 -0
  121. package/dist/__tests__/tools/worktree-handlers.test.js +297 -0
  122. package/dist/__tests__/tools/worktree-handlers.test.js.map +1 -0
  123. package/dist/__tests__/utils/cache.test.d.ts +2 -0
  124. package/dist/__tests__/utils/cache.test.d.ts.map +1 -0
  125. package/dist/__tests__/utils/cache.test.js +147 -0
  126. package/dist/__tests__/utils/cache.test.js.map +1 -0
  127. package/dist/__tests__/utils/concurrency.test.d.ts +2 -0
  128. package/dist/__tests__/utils/concurrency.test.d.ts.map +1 -0
  129. package/dist/__tests__/utils/concurrency.test.js +83 -0
  130. package/dist/__tests__/utils/concurrency.test.js.map +1 -0
  131. package/dist/__tests__/utils/errors.test.d.ts +2 -0
  132. package/dist/__tests__/utils/errors.test.d.ts.map +1 -0
  133. package/dist/__tests__/utils/errors.test.js +136 -0
  134. package/dist/__tests__/utils/errors.test.js.map +1 -0
  135. package/dist/__tests__/utils/logger.test.d.ts +2 -0
  136. package/dist/__tests__/utils/logger.test.d.ts.map +1 -0
  137. package/dist/__tests__/utils/logger.test.js +131 -0
  138. package/dist/__tests__/utils/logger.test.js.map +1 -0
  139. package/dist/__tests__/utils/type-guards.test.d.ts +2 -0
  140. package/dist/__tests__/utils/type-guards.test.d.ts.map +1 -0
  141. package/dist/__tests__/utils/type-guards.test.js +80 -0
  142. package/dist/__tests__/utils/type-guards.test.js.map +1 -0
  143. package/dist/__tests__/worktree/worktree-manager.test.d.ts +2 -0
  144. package/dist/__tests__/worktree/worktree-manager.test.d.ts.map +1 -0
  145. package/dist/__tests__/worktree/worktree-manager.test.js +403 -0
  146. package/dist/__tests__/worktree/worktree-manager.test.js.map +1 -0
  147. package/dist/config.d.ts +191 -0
  148. package/dist/config.d.ts.map +1 -0
  149. package/dist/config.js +788 -0
  150. package/dist/config.js.map +1 -0
  151. package/dist/dashboard/beads.d.ts +35 -0
  152. package/dist/dashboard/beads.d.ts.map +1 -0
  153. package/dist/dashboard/beads.js +102 -0
  154. package/dist/dashboard/beads.js.map +1 -0
  155. package/dist/dashboard/events.d.ts +64 -0
  156. package/dist/dashboard/events.d.ts.map +1 -0
  157. package/dist/dashboard/events.js +211 -0
  158. package/dist/dashboard/events.js.map +1 -0
  159. package/dist/dashboard/index.d.ts +69 -0
  160. package/dist/dashboard/index.d.ts.map +1 -0
  161. package/dist/dashboard/index.js +93 -0
  162. package/dist/dashboard/index.js.map +1 -0
  163. package/dist/dashboard/routes.d.ts +6 -0
  164. package/dist/dashboard/routes.d.ts.map +1 -0
  165. package/dist/dashboard/routes.js +505 -0
  166. package/dist/dashboard/routes.js.map +1 -0
  167. package/dist/dashboard/server.d.ts +27 -0
  168. package/dist/dashboard/server.d.ts.map +1 -0
  169. package/dist/dashboard/server.js +72 -0
  170. package/dist/dashboard/server.js.map +1 -0
  171. package/dist/dashboard/state.d.ts +172 -0
  172. package/dist/dashboard/state.d.ts.map +1 -0
  173. package/dist/dashboard/state.js +362 -0
  174. package/dist/dashboard/state.js.map +1 -0
  175. package/dist/dashboard/token-tracking.d.ts +143 -0
  176. package/dist/dashboard/token-tracking.d.ts.map +1 -0
  177. package/dist/dashboard/token-tracking.js +363 -0
  178. package/dist/dashboard/token-tracking.js.map +1 -0
  179. package/dist/dashboard/ui.d.ts +6 -0
  180. package/dist/dashboard/ui.d.ts.map +1 -0
  181. package/dist/dashboard/ui.js +2710 -0
  182. package/dist/dashboard/ui.js.map +1 -0
  183. package/dist/embeddings/gemini.d.ts +28 -0
  184. package/dist/embeddings/gemini.d.ts.map +1 -0
  185. package/dist/embeddings/gemini.js +165 -0
  186. package/dist/embeddings/gemini.js.map +1 -0
  187. package/dist/embeddings/index.d.ts +34 -0
  188. package/dist/embeddings/index.d.ts.map +1 -0
  189. package/dist/embeddings/index.js +120 -0
  190. package/dist/embeddings/index.js.map +1 -0
  191. package/dist/embeddings/ollama.d.ts +22 -0
  192. package/dist/embeddings/ollama.d.ts.map +1 -0
  193. package/dist/embeddings/ollama.js +179 -0
  194. package/dist/embeddings/ollama.js.map +1 -0
  195. package/dist/embeddings/rate-limiter.d.ts +75 -0
  196. package/dist/embeddings/rate-limiter.d.ts.map +1 -0
  197. package/dist/embeddings/rate-limiter.js +145 -0
  198. package/dist/embeddings/rate-limiter.js.map +1 -0
  199. package/dist/embeddings/retry.d.ts +20 -0
  200. package/dist/embeddings/retry.d.ts.map +1 -0
  201. package/dist/embeddings/retry.js +227 -0
  202. package/dist/embeddings/retry.js.map +1 -0
  203. package/dist/embeddings/types.d.ts +103 -0
  204. package/dist/embeddings/types.d.ts.map +1 -0
  205. package/dist/embeddings/types.js +23 -0
  206. package/dist/embeddings/types.js.map +1 -0
  207. package/dist/index.d.ts +12 -0
  208. package/dist/index.d.ts.map +1 -0
  209. package/dist/index.js +2028 -0
  210. package/dist/index.js.map +1 -0
  211. package/dist/memory/index.d.ts +63 -0
  212. package/dist/memory/index.d.ts.map +1 -0
  213. package/dist/memory/index.js +168 -0
  214. package/dist/memory/index.js.map +1 -0
  215. package/dist/search/ast-chunker.d.ts +29 -0
  216. package/dist/search/ast-chunker.d.ts.map +1 -0
  217. package/dist/search/ast-chunker.js +236 -0
  218. package/dist/search/ast-chunker.js.map +1 -0
  219. package/dist/search/chunk-utils.d.ts +24 -0
  220. package/dist/search/chunk-utils.d.ts.map +1 -0
  221. package/dist/search/chunk-utils.js +41 -0
  222. package/dist/search/chunk-utils.js.map +1 -0
  223. package/dist/search/clustering.d.ts +77 -0
  224. package/dist/search/clustering.d.ts.map +1 -0
  225. package/dist/search/clustering.js +455 -0
  226. package/dist/search/clustering.js.map +1 -0
  227. package/dist/search/indexer.d.ts +377 -0
  228. package/dist/search/indexer.d.ts.map +1 -0
  229. package/dist/search/indexer.js +1557 -0
  230. package/dist/search/indexer.js.map +1 -0
  231. package/dist/search/tree-sitter-chunker.d.ts +64 -0
  232. package/dist/search/tree-sitter-chunker.d.ts.map +1 -0
  233. package/dist/search/tree-sitter-chunker.js +412 -0
  234. package/dist/search/tree-sitter-chunker.js.map +1 -0
  235. package/dist/symbols/index.d.ts +14 -0
  236. package/dist/symbols/index.d.ts.map +1 -0
  237. package/dist/symbols/index.js +19 -0
  238. package/dist/symbols/index.js.map +1 -0
  239. package/dist/symbols/name-path.d.ts +113 -0
  240. package/dist/symbols/name-path.d.ts.map +1 -0
  241. package/dist/symbols/name-path.js +194 -0
  242. package/dist/symbols/name-path.js.map +1 -0
  243. package/dist/symbols/pattern-search.d.ts +14 -0
  244. package/dist/symbols/pattern-search.d.ts.map +1 -0
  245. package/dist/symbols/pattern-search.js +224 -0
  246. package/dist/symbols/pattern-search.js.map +1 -0
  247. package/dist/symbols/reference-finder.d.ts +38 -0
  248. package/dist/symbols/reference-finder.d.ts.map +1 -0
  249. package/dist/symbols/reference-finder.js +376 -0
  250. package/dist/symbols/reference-finder.js.map +1 -0
  251. package/dist/symbols/symbol-editor.d.ts +81 -0
  252. package/dist/symbols/symbol-editor.d.ts.map +1 -0
  253. package/dist/symbols/symbol-editor.js +257 -0
  254. package/dist/symbols/symbol-editor.js.map +1 -0
  255. package/dist/symbols/symbol-extractor.d.ts +49 -0
  256. package/dist/symbols/symbol-extractor.d.ts.map +1 -0
  257. package/dist/symbols/symbol-extractor.js +593 -0
  258. package/dist/symbols/symbol-extractor.js.map +1 -0
  259. package/dist/symbols/symbol-renamer.d.ts +81 -0
  260. package/dist/symbols/symbol-renamer.d.ts.map +1 -0
  261. package/dist/symbols/symbol-renamer.js +204 -0
  262. package/dist/symbols/symbol-renamer.js.map +1 -0
  263. package/dist/symbols/types.d.ts +234 -0
  264. package/dist/symbols/types.d.ts.map +1 -0
  265. package/dist/symbols/types.js +106 -0
  266. package/dist/symbols/types.js.map +1 -0
  267. package/dist/tools/clustering-handlers.d.ts +70 -0
  268. package/dist/tools/clustering-handlers.d.ts.map +1 -0
  269. package/dist/tools/clustering-handlers.js +150 -0
  270. package/dist/tools/clustering-handlers.js.map +1 -0
  271. package/dist/tools/commit-handlers.d.ts +77 -0
  272. package/dist/tools/commit-handlers.d.ts.map +1 -0
  273. package/dist/tools/commit-handlers.js +251 -0
  274. package/dist/tools/commit-handlers.js.map +1 -0
  275. package/dist/tools/index-handlers.d.ts +31 -0
  276. package/dist/tools/index-handlers.d.ts.map +1 -0
  277. package/dist/tools/index-handlers.js +53 -0
  278. package/dist/tools/index-handlers.js.map +1 -0
  279. package/dist/tools/instructions-handlers.d.ts +25 -0
  280. package/dist/tools/instructions-handlers.d.ts.map +1 -0
  281. package/dist/tools/instructions-handlers.js +36 -0
  282. package/dist/tools/instructions-handlers.js.map +1 -0
  283. package/dist/tools/memory-handlers.d.ts +95 -0
  284. package/dist/tools/memory-handlers.d.ts.map +1 -0
  285. package/dist/tools/memory-handlers.js +126 -0
  286. package/dist/tools/memory-handlers.js.map +1 -0
  287. package/dist/tools/search-handlers.d.ts +51 -0
  288. package/dist/tools/search-handlers.d.ts.map +1 -0
  289. package/dist/tools/search-handlers.js +128 -0
  290. package/dist/tools/search-handlers.js.map +1 -0
  291. package/dist/tools/symbol-handlers.d.ts +171 -0
  292. package/dist/tools/symbol-handlers.d.ts.map +1 -0
  293. package/dist/tools/symbol-handlers.js +456 -0
  294. package/dist/tools/symbol-handlers.js.map +1 -0
  295. package/dist/tools/types.d.ts +32 -0
  296. package/dist/tools/types.d.ts.map +1 -0
  297. package/dist/tools/types.js +17 -0
  298. package/dist/tools/types.js.map +1 -0
  299. package/dist/tools/worktree-handlers.d.ts +96 -0
  300. package/dist/tools/worktree-handlers.d.ts.map +1 -0
  301. package/dist/tools/worktree-handlers.js +186 -0
  302. package/dist/tools/worktree-handlers.js.map +1 -0
  303. package/dist/utils/cache.d.ts +77 -0
  304. package/dist/utils/cache.d.ts.map +1 -0
  305. package/dist/utils/cache.js +134 -0
  306. package/dist/utils/cache.js.map +1 -0
  307. package/dist/utils/concurrency.d.ts +32 -0
  308. package/dist/utils/concurrency.d.ts.map +1 -0
  309. package/dist/utils/concurrency.js +57 -0
  310. package/dist/utils/concurrency.js.map +1 -0
  311. package/dist/utils/errors.d.ts +36 -0
  312. package/dist/utils/errors.d.ts.map +1 -0
  313. package/dist/utils/errors.js +91 -0
  314. package/dist/utils/errors.js.map +1 -0
  315. package/dist/utils/logger.d.ts +37 -0
  316. package/dist/utils/logger.d.ts.map +1 -0
  317. package/dist/utils/logger.js +114 -0
  318. package/dist/utils/logger.js.map +1 -0
  319. package/dist/utils/type-guards.d.ts +17 -0
  320. package/dist/utils/type-guards.d.ts.map +1 -0
  321. package/dist/utils/type-guards.js +25 -0
  322. package/dist/utils/type-guards.js.map +1 -0
  323. package/dist/worktree/index.d.ts +6 -0
  324. package/dist/worktree/index.d.ts.map +1 -0
  325. package/dist/worktree/index.js +6 -0
  326. package/dist/worktree/index.js.map +1 -0
  327. package/dist/worktree/types.d.ts +101 -0
  328. package/dist/worktree/types.d.ts.map +1 -0
  329. package/dist/worktree/types.js +6 -0
  330. package/dist/worktree/types.js.map +1 -0
  331. package/dist/worktree/worktree-manager.d.ts +80 -0
  332. package/dist/worktree/worktree-manager.d.ts.map +1 -0
  333. package/dist/worktree/worktree-manager.js +407 -0
  334. package/dist/worktree/worktree-manager.js.map +1 -0
  335. package/package.json +87 -0
  336. package/scripts/postinstall.js +48 -0
package/dist/config.js ADDED
@@ -0,0 +1,788 @@
1
+ import * as fs from 'fs/promises';
2
+ import * as path from 'path';
3
+ import { z } from 'zod';
4
+ /**
5
+ * Zod schema for configuration validation
6
+ */
7
+ const ChunkingConfigSchema = z.object({
8
+ maxLines: z.number().min(10).max(500).optional(),
9
+ overlap: z.number().min(0).max(50).optional(),
10
+ });
11
+ const SearchConfigSchema = z.object({
12
+ semanticWeight: z.number().min(0).max(1).optional(),
13
+ keywordWeight: z.number().min(0).max(1).optional(),
14
+ /** Automatically reindex when stale files are detected before search (default: true) */
15
+ autoReindex: z.boolean().optional(),
16
+ });
17
+ const EmbeddingConfigSchema = z.object({
18
+ backend: z.enum(['ollama', 'gemini']).optional(),
19
+ model: z.string().optional(),
20
+ /** Number of concurrent requests to Ollama (default: 10). Increase if your system has capacity. */
21
+ ollamaConcurrency: z.number().min(1).max(200).optional(),
22
+ });
23
+ const DashboardConfigSchema = z.object({
24
+ enabled: z.boolean().optional(),
25
+ port: z.number().min(1024).max(65535).optional(),
26
+ openBrowser: z.boolean().optional(),
27
+ });
28
+ const IndexingConfigSchema = z.object({
29
+ /** Delay in milliseconds between embedding batches (default: 0) */
30
+ batchDelayMs: z.number().min(0).max(10000).optional(),
31
+ /** Number of chunks to embed per batch (default: 32). Higher values reduce overhead but use more memory. */
32
+ batchSize: z.number().min(1).max(1000).optional(),
33
+ });
34
+ const ConfigSchema = z.object({
35
+ patterns: z.array(z.string()).optional(),
36
+ excludePatterns: z.array(z.string()).optional(),
37
+ embedding: EmbeddingConfigSchema.optional(),
38
+ chunking: ChunkingConfigSchema.optional(),
39
+ search: SearchConfigSchema.optional(),
40
+ dashboard: DashboardConfigSchema.optional(),
41
+ indexing: IndexingConfigSchema.optional(),
42
+ instructions: z.string().optional(),
43
+ });
44
+ const DEFAULT_PATTERNS = [
45
+ '**/*.ts',
46
+ '**/*.tsx',
47
+ '**/*.js',
48
+ '**/*.jsx',
49
+ '**/*.py',
50
+ '**/*.go',
51
+ '**/*.rs',
52
+ '**/*.java',
53
+ '**/*.rb',
54
+ '**/*.php',
55
+ '**/*.c',
56
+ '**/*.cpp',
57
+ '**/*.h',
58
+ '**/*.hpp',
59
+ '**/*.cs',
60
+ '**/*.swift',
61
+ '**/*.kt',
62
+ ];
63
+ const DEFAULT_EXCLUDE_PATTERNS = [
64
+ '**/node_modules/**',
65
+ '**/dist/**',
66
+ '**/.git/**',
67
+ '**/build/**',
68
+ '**/target/**',
69
+ '**/__pycache__/**',
70
+ '**/venv/**',
71
+ '**/.venv/**',
72
+ '**/vendor/**',
73
+ '**/*.min.js',
74
+ '**/*.min.css',
75
+ ];
76
+ /**
77
+ * Default chunking configuration
78
+ */
79
+ export const DEFAULT_CHUNKING = {
80
+ maxLines: 100,
81
+ overlap: 20,
82
+ };
83
+ /**
84
+ * Default search configuration
85
+ */
86
+ export const DEFAULT_SEARCH = {
87
+ semanticWeight: 0.7,
88
+ keywordWeight: 0.3,
89
+ autoReindex: true,
90
+ };
91
+ /**
92
+ * Default dashboard configuration
93
+ */
94
+ export const DEFAULT_DASHBOARD = {
95
+ enabled: true,
96
+ port: 24300,
97
+ openBrowser: true,
98
+ };
99
+ /**
100
+ * Default indexing configuration
101
+ */
102
+ export const DEFAULT_INDEXING = {
103
+ batchDelayMs: 0,
104
+ batchSize: 256,
105
+ };
106
+ export const DEFAULT_CONFIG = {
107
+ patterns: DEFAULT_PATTERNS,
108
+ excludePatterns: DEFAULT_EXCLUDE_PATTERNS,
109
+ chunking: DEFAULT_CHUNKING,
110
+ search: DEFAULT_SEARCH,
111
+ dashboard: DEFAULT_DASHBOARD,
112
+ indexing: DEFAULT_INDEXING,
113
+ };
114
+ const CONFIG_FILENAMES = ['.glancey.json', 'glancey.config.json'];
115
+ /**
116
+ * Local config filename for user-specific overrides (gitignored)
117
+ * Settings saved from the dashboard are written here instead of the main config
118
+ */
119
+ const LOCAL_CONFIG_FILENAME = '.glancey.local.json';
120
+ /**
121
+ * Load and validate configuration from project directory
122
+ */
123
+ /**
124
+ * Format a Zod validation error into a user-friendly message
125
+ */
126
+ function formatValidationError(error, rawConfig) {
127
+ const pathArray = error.path.map((p) => String(p));
128
+ const fieldPath = pathArray.join('.');
129
+ const rawValue = getValueAtPath(rawConfig, pathArray);
130
+ const rawValueStr = rawValue === undefined ? 'undefined' : JSON.stringify(rawValue);
131
+ switch (error.code) {
132
+ case 'invalid_type': {
133
+ const expected = error.expected;
134
+ const suggestion = getSuggestionForType(fieldPath, String(expected), rawValue);
135
+ return ` - ${fieldPath}: Expected ${expected}, got ${typeof rawValue} ${rawValueStr}${suggestion}`;
136
+ }
137
+ case 'too_small': {
138
+ const issueMin = error;
139
+ const suggestion = getSuggestionForRange(fieldPath, 'minimum', issueMin.minimum);
140
+ return ` - ${fieldPath}: Value ${rawValueStr} is below minimum ${issueMin.minimum}${suggestion}`;
141
+ }
142
+ case 'too_big': {
143
+ const issueMax = error;
144
+ const suggestion = getSuggestionForRange(fieldPath, 'maximum', issueMax.maximum);
145
+ return ` - ${fieldPath}: Value ${rawValueStr} exceeds maximum ${issueMax.maximum}${suggestion}`;
146
+ }
147
+ case 'invalid_value': {
148
+ // Handle enum validation errors
149
+ const issueVal = error;
150
+ if (issueVal.values) {
151
+ const options = issueVal.values.map((o) => `'${String(o)}'`).join(', ');
152
+ return ` - ${fieldPath}: Invalid value ${rawValueStr}. Valid options: ${options}`;
153
+ }
154
+ return ` - ${fieldPath}: Invalid value ${rawValueStr}`;
155
+ }
156
+ default:
157
+ return ` - ${fieldPath}: ${error.message}`;
158
+ }
159
+ }
160
+ /**
161
+ * Get the value at a path in an object
162
+ */
163
+ function getValueAtPath(obj, pathSegments) {
164
+ let current = obj;
165
+ for (const segment of pathSegments) {
166
+ if (current === null || typeof current !== 'object') {
167
+ return undefined;
168
+ }
169
+ current = current[segment];
170
+ }
171
+ return current;
172
+ }
173
+ /**
174
+ * Get suggestions for common type mistakes
175
+ */
176
+ function getSuggestionForType(fieldPath, expected, rawValue) {
177
+ // Check for string that looks like a number
178
+ if (expected === 'number' && typeof rawValue === 'string') {
179
+ const num = Number(rawValue);
180
+ if (!isNaN(num)) {
181
+ return `\n Suggestion: Remove quotes to use numeric value: ${num}`;
182
+ }
183
+ }
184
+ // Check for string that looks like a boolean
185
+ if (expected === 'boolean' && typeof rawValue === 'string') {
186
+ const lower = rawValue.toLowerCase();
187
+ if (lower === 'true' || lower === 'false') {
188
+ return `\n Suggestion: Remove quotes to use boolean value: ${lower}`;
189
+ }
190
+ }
191
+ // Check for array expected but got single value
192
+ if (expected === 'array' && typeof rawValue === 'string') {
193
+ return `\n Suggestion: Wrap the value in an array: ["${rawValue}"]`;
194
+ }
195
+ return '';
196
+ }
197
+ /**
198
+ * Get suggestions for range violations
199
+ */
200
+ function getSuggestionForRange(fieldPath, boundType, _bound) {
201
+ const fieldSuggestions = {
202
+ 'chunking.maxLines': boundType === 'minimum'
203
+ ? '\n Suggestion: Use a value between 10 and 500 lines per chunk'
204
+ : '\n Suggestion: Use a value between 10 and 500 lines per chunk',
205
+ 'chunking.overlap': boundType === 'minimum'
206
+ ? '\n Suggestion: Use a value between 0 and 50 lines for overlap'
207
+ : '\n Suggestion: Use a value between 0 and 50 lines for overlap',
208
+ 'search.semanticWeight': '\n Suggestion: Use a value between 0.0 and 1.0 for search weights',
209
+ 'search.keywordWeight': '\n Suggestion: Use a value between 0.0 and 1.0 for search weights',
210
+ 'dashboard.port': '\n Suggestion: Use a port number between 1024 and 65535',
211
+ };
212
+ return fieldSuggestions[fieldPath] || '';
213
+ }
214
+ /**
215
+ * Print formatted validation warnings to console
216
+ */
217
+ function printValidationWarnings(filename, errors, rawConfig) {
218
+ console.warn(`[glancey] Warning: Invalid config in ${filename}`);
219
+ for (const error of errors) {
220
+ console.warn(formatValidationError(error, rawConfig));
221
+ }
222
+ console.warn(' Using default values for invalid fields.');
223
+ }
224
+ /**
225
+ * Extract valid configuration values from a raw config object.
226
+ * This allows us to use valid fields while falling back to defaults for invalid ones.
227
+ */
228
+ function extractValidConfig(rawConfig) {
229
+ if (typeof rawConfig !== 'object' || rawConfig === null) {
230
+ return {};
231
+ }
232
+ const config = rawConfig;
233
+ const result = {};
234
+ // Try to parse each section independently
235
+ if (config.patterns !== undefined) {
236
+ const patternsResult = z.array(z.string()).safeParse(config.patterns);
237
+ if (patternsResult.success) {
238
+ result.patterns = patternsResult.data;
239
+ }
240
+ }
241
+ if (config.excludePatterns !== undefined) {
242
+ const excludeResult = z.array(z.string()).safeParse(config.excludePatterns);
243
+ if (excludeResult.success) {
244
+ result.excludePatterns = excludeResult.data;
245
+ }
246
+ }
247
+ if (config.instructions !== undefined) {
248
+ const instructionsResult = z.string().safeParse(config.instructions);
249
+ if (instructionsResult.success) {
250
+ result.instructions = instructionsResult.data;
251
+ }
252
+ }
253
+ if (config.embedding !== undefined) {
254
+ const embeddingResult = EmbeddingConfigSchema.safeParse(config.embedding);
255
+ if (embeddingResult.success) {
256
+ result.embedding = embeddingResult.data;
257
+ }
258
+ }
259
+ if (config.chunking !== undefined) {
260
+ // Try to extract valid individual fields from chunking
261
+ const chunkingResult = extractValidChunking(config.chunking);
262
+ if (Object.keys(chunkingResult).length > 0) {
263
+ result.chunking = chunkingResult;
264
+ }
265
+ }
266
+ if (config.search !== undefined) {
267
+ // Try to extract valid individual fields from search
268
+ const searchResult = extractValidSearch(config.search);
269
+ if (Object.keys(searchResult).length > 0) {
270
+ result.search = searchResult;
271
+ }
272
+ }
273
+ if (config.dashboard !== undefined) {
274
+ // Try to extract valid individual fields from dashboard
275
+ const dashboardResult = extractValidDashboard(config.dashboard);
276
+ if (Object.keys(dashboardResult).length > 0) {
277
+ result.dashboard = dashboardResult;
278
+ }
279
+ }
280
+ if (config.indexing !== undefined) {
281
+ // Try to extract valid individual fields from indexing
282
+ const indexingResult = extractValidIndexing(config.indexing);
283
+ if (Object.keys(indexingResult).length > 0) {
284
+ result.indexing = indexingResult;
285
+ }
286
+ }
287
+ return result;
288
+ }
289
+ /**
290
+ * Extract valid chunking config fields
291
+ */
292
+ function extractValidChunking(rawChunking) {
293
+ if (typeof rawChunking !== 'object' || rawChunking === null) {
294
+ return {};
295
+ }
296
+ const chunking = rawChunking;
297
+ const result = {};
298
+ if (chunking.maxLines !== undefined) {
299
+ const maxLinesResult = z.number().min(10).max(500).safeParse(chunking.maxLines);
300
+ if (maxLinesResult.success) {
301
+ result.maxLines = maxLinesResult.data;
302
+ }
303
+ }
304
+ if (chunking.overlap !== undefined) {
305
+ const overlapResult = z.number().min(0).max(50).safeParse(chunking.overlap);
306
+ if (overlapResult.success) {
307
+ result.overlap = overlapResult.data;
308
+ }
309
+ }
310
+ return result;
311
+ }
312
+ /**
313
+ * Extract valid search config fields
314
+ */
315
+ function extractValidSearch(rawSearch) {
316
+ if (typeof rawSearch !== 'object' || rawSearch === null) {
317
+ return {};
318
+ }
319
+ const search = rawSearch;
320
+ const result = {};
321
+ if (search.semanticWeight !== undefined) {
322
+ const semanticResult = z.number().min(0).max(1).safeParse(search.semanticWeight);
323
+ if (semanticResult.success) {
324
+ result.semanticWeight = semanticResult.data;
325
+ }
326
+ }
327
+ if (search.keywordWeight !== undefined) {
328
+ const keywordResult = z.number().min(0).max(1).safeParse(search.keywordWeight);
329
+ if (keywordResult.success) {
330
+ result.keywordWeight = keywordResult.data;
331
+ }
332
+ }
333
+ if (search.autoReindex !== undefined) {
334
+ const autoReindexResult = z.boolean().safeParse(search.autoReindex);
335
+ if (autoReindexResult.success) {
336
+ result.autoReindex = autoReindexResult.data;
337
+ }
338
+ }
339
+ return result;
340
+ }
341
+ /**
342
+ * Extract valid dashboard config fields
343
+ */
344
+ function extractValidDashboard(rawDashboard) {
345
+ if (typeof rawDashboard !== 'object' || rawDashboard === null) {
346
+ return {};
347
+ }
348
+ const dashboard = rawDashboard;
349
+ const result = {};
350
+ if (dashboard.enabled !== undefined) {
351
+ const enabledResult = z.boolean().safeParse(dashboard.enabled);
352
+ if (enabledResult.success) {
353
+ result.enabled = enabledResult.data;
354
+ }
355
+ }
356
+ if (dashboard.port !== undefined) {
357
+ const portResult = z.number().min(1024).max(65535).safeParse(dashboard.port);
358
+ if (portResult.success) {
359
+ result.port = portResult.data;
360
+ }
361
+ }
362
+ if (dashboard.openBrowser !== undefined) {
363
+ const openBrowserResult = z.boolean().safeParse(dashboard.openBrowser);
364
+ if (openBrowserResult.success) {
365
+ result.openBrowser = openBrowserResult.data;
366
+ }
367
+ }
368
+ return result;
369
+ }
370
+ /**
371
+ * Extract valid indexing config fields
372
+ */
373
+ function extractValidIndexing(rawIndexing) {
374
+ if (typeof rawIndexing !== 'object' || rawIndexing === null) {
375
+ return {};
376
+ }
377
+ const indexing = rawIndexing;
378
+ const result = {};
379
+ if (indexing.batchDelayMs !== undefined) {
380
+ const batchDelayResult = z.number().min(0).max(10000).safeParse(indexing.batchDelayMs);
381
+ if (batchDelayResult.success) {
382
+ result.batchDelayMs = batchDelayResult.data;
383
+ }
384
+ }
385
+ if (indexing.batchSize !== undefined) {
386
+ const batchSizeResult = z.number().min(1).max(100).safeParse(indexing.batchSize);
387
+ if (batchSizeResult.success) {
388
+ result.batchSize = batchSizeResult.data;
389
+ }
390
+ }
391
+ return result;
392
+ }
393
+ /**
394
+ * Load local config overrides (user-specific settings, gitignored)
395
+ */
396
+ async function loadLocalConfig(projectPath) {
397
+ const localConfigPath = path.join(projectPath, LOCAL_CONFIG_FILENAME);
398
+ try {
399
+ const content = await fs.readFile(localConfigPath, 'utf-8');
400
+ const rawConfig = JSON.parse(content);
401
+ // Validate with Zod (lenient - just extract valid parts)
402
+ const result = ConfigSchema.safeParse(rawConfig);
403
+ if (result.success) {
404
+ return result.data;
405
+ }
406
+ // Try to salvage valid parts
407
+ return extractValidConfig(rawConfig);
408
+ }
409
+ catch {
410
+ // Local config doesn't exist, return empty
411
+ return {};
412
+ }
413
+ }
414
+ /**
415
+ * Deep merge two config objects, with localConfig taking precedence
416
+ */
417
+ function mergeConfigs(baseConfig, localConfig) {
418
+ return {
419
+ patterns: localConfig.patterns || baseConfig.patterns,
420
+ excludePatterns: localConfig.excludePatterns || baseConfig.excludePatterns,
421
+ embedding: {
422
+ ...baseConfig.embedding,
423
+ ...localConfig.embedding,
424
+ },
425
+ chunking: {
426
+ ...baseConfig.chunking,
427
+ ...localConfig.chunking,
428
+ },
429
+ search: {
430
+ ...baseConfig.search,
431
+ ...localConfig.search,
432
+ },
433
+ dashboard: {
434
+ ...baseConfig.dashboard,
435
+ ...localConfig.dashboard,
436
+ },
437
+ indexing: {
438
+ ...baseConfig.indexing,
439
+ ...localConfig.indexing,
440
+ },
441
+ instructions: localConfig.instructions ?? baseConfig.instructions,
442
+ };
443
+ }
444
+ export async function loadConfig(projectPath) {
445
+ let baseConfig = DEFAULT_CONFIG;
446
+ // Load project config
447
+ for (const filename of CONFIG_FILENAMES) {
448
+ const configPath = path.join(projectPath, filename);
449
+ try {
450
+ const content = await fs.readFile(configPath, 'utf-8');
451
+ const rawConfig = JSON.parse(content);
452
+ // Validate with Zod
453
+ const result = ConfigSchema.safeParse(rawConfig);
454
+ if (!result.success) {
455
+ // Print detailed validation warnings
456
+ printValidationWarnings(filename, result.error.issues, rawConfig);
457
+ // Try to salvage valid parts of the config by validating each section separately
458
+ const validConfig = extractValidConfig(rawConfig);
459
+ baseConfig = {
460
+ patterns: validConfig.patterns || DEFAULT_PATTERNS,
461
+ excludePatterns: validConfig.excludePatterns || DEFAULT_EXCLUDE_PATTERNS,
462
+ embedding: validConfig.embedding,
463
+ chunking: {
464
+ ...DEFAULT_CHUNKING,
465
+ ...validConfig.chunking,
466
+ },
467
+ search: {
468
+ ...DEFAULT_SEARCH,
469
+ ...validConfig.search,
470
+ },
471
+ dashboard: {
472
+ ...DEFAULT_DASHBOARD,
473
+ ...validConfig.dashboard,
474
+ },
475
+ indexing: {
476
+ ...DEFAULT_INDEXING,
477
+ ...validConfig.indexing,
478
+ },
479
+ instructions: validConfig.instructions,
480
+ };
481
+ break;
482
+ }
483
+ const userConfig = result.data;
484
+ baseConfig = {
485
+ patterns: userConfig.patterns || DEFAULT_PATTERNS,
486
+ excludePatterns: userConfig.excludePatterns || DEFAULT_EXCLUDE_PATTERNS,
487
+ embedding: userConfig.embedding,
488
+ chunking: {
489
+ ...DEFAULT_CHUNKING,
490
+ ...userConfig.chunking,
491
+ },
492
+ search: {
493
+ ...DEFAULT_SEARCH,
494
+ ...userConfig.search,
495
+ },
496
+ dashboard: {
497
+ ...DEFAULT_DASHBOARD,
498
+ ...userConfig.dashboard,
499
+ },
500
+ indexing: {
501
+ ...DEFAULT_INDEXING,
502
+ ...userConfig.indexing,
503
+ },
504
+ instructions: userConfig.instructions,
505
+ };
506
+ break;
507
+ }
508
+ catch (error) {
509
+ // Check if it's a JSON parse error
510
+ if (error instanceof SyntaxError) {
511
+ console.warn(`[glancey] Warning: Invalid JSON in ${filename}`);
512
+ console.warn(` - ${error.message}`);
513
+ console.warn(' Suggestion: Check for trailing commas, missing quotes, or other JSON syntax errors.');
514
+ continue;
515
+ }
516
+ // Config file doesn't exist, continue to next
517
+ }
518
+ }
519
+ // Load and merge local config overrides
520
+ const localConfig = await loadLocalConfig(projectPath);
521
+ return mergeConfigs(baseConfig, localConfig);
522
+ }
523
+ /**
524
+ * Get default file patterns
525
+ */
526
+ export function getDefaultPatterns() {
527
+ return [...DEFAULT_PATTERNS];
528
+ }
529
+ /**
530
+ * Get default exclude patterns
531
+ */
532
+ export function getDefaultExcludePatterns() {
533
+ return [...DEFAULT_EXCLUDE_PATTERNS];
534
+ }
535
+ /**
536
+ * Get chunking config with defaults
537
+ */
538
+ export function getChunkingConfig(config) {
539
+ return {
540
+ ...DEFAULT_CHUNKING,
541
+ ...config.chunking,
542
+ };
543
+ }
544
+ /**
545
+ * Get search config with defaults
546
+ */
547
+ export function getSearchConfig(config) {
548
+ return {
549
+ ...DEFAULT_SEARCH,
550
+ ...config.search,
551
+ };
552
+ }
553
+ /**
554
+ * Get dashboard config with defaults
555
+ */
556
+ export function getDashboardConfig(config) {
557
+ return {
558
+ ...DEFAULT_DASHBOARD,
559
+ ...config.dashboard,
560
+ };
561
+ }
562
+ /**
563
+ * Get indexing config with defaults
564
+ */
565
+ export function getIndexingConfig(config) {
566
+ return {
567
+ ...DEFAULT_INDEXING,
568
+ ...config.indexing,
569
+ };
570
+ }
571
+ /**
572
+ * Get project instructions from config
573
+ */
574
+ export function getInstructions(config) {
575
+ return config.instructions;
576
+ }
577
+ /**
578
+ * Load secrets from .glancey/secrets.json
579
+ */
580
+ export async function loadSecrets(projectPath) {
581
+ const secretsPath = path.join(projectPath, '.glancey', 'secrets.json');
582
+ try {
583
+ const content = await fs.readFile(secretsPath, 'utf-8');
584
+ return JSON.parse(content);
585
+ }
586
+ catch {
587
+ return {};
588
+ }
589
+ }
590
+ /**
591
+ * Save secrets to .glancey/secrets.json
592
+ */
593
+ export async function saveSecrets(projectPath, secrets) {
594
+ const lanceDir = path.join(projectPath, '.glancey');
595
+ const secretsPath = path.join(lanceDir, 'secrets.json');
596
+ // Ensure .glancey directory exists
597
+ await fs.mkdir(lanceDir, { recursive: true });
598
+ // Load existing secrets and merge
599
+ const existing = await loadSecrets(projectPath);
600
+ const merged = { ...existing, ...secrets };
601
+ await fs.writeFile(secretsPath, JSON.stringify(merged, null, 2));
602
+ }
603
+ /**
604
+ * Save embedding settings from dashboard
605
+ * - Stores backend preference in .glancey.local.json (gitignored, user-specific)
606
+ * - Stores API key in .glancey/secrets.json (gitignored)
607
+ */
608
+ export async function saveEmbeddingSettings(projectPath, settings) {
609
+ // Load existing local config (user-specific overrides)
610
+ const localConfigPath = path.join(projectPath, LOCAL_CONFIG_FILENAME);
611
+ let localConfig = {};
612
+ try {
613
+ const content = await fs.readFile(localConfigPath, 'utf-8');
614
+ localConfig = JSON.parse(content);
615
+ }
616
+ catch {
617
+ // File doesn't exist, start fresh
618
+ }
619
+ // Update embedding config
620
+ localConfig.embedding = {
621
+ ...localConfig.embedding,
622
+ backend: settings.backend,
623
+ };
624
+ // Update ollamaConcurrency if provided
625
+ if (settings.ollamaConcurrency !== undefined) {
626
+ localConfig.embedding.ollamaConcurrency = settings.ollamaConcurrency;
627
+ }
628
+ // Update indexing batch size if provided
629
+ if (settings.batchSize !== undefined) {
630
+ localConfig.indexing = {
631
+ ...localConfig.indexing,
632
+ batchSize: settings.batchSize,
633
+ };
634
+ }
635
+ // Save to local config (gitignored, user-specific)
636
+ await fs.writeFile(localConfigPath, JSON.stringify(localConfig, null, 2));
637
+ // Save API key to secrets if provided
638
+ if (settings.apiKey) {
639
+ if (settings.backend === 'gemini') {
640
+ await saveSecrets(projectPath, { geminiApiKey: settings.apiKey });
641
+ }
642
+ }
643
+ }
644
+ /**
645
+ * Get current embedding settings including secrets
646
+ */
647
+ export async function getEmbeddingSettings(projectPath) {
648
+ const config = await loadConfig(projectPath);
649
+ const secrets = await loadSecrets(projectPath);
650
+ const backend = config.embedding?.backend || 'ollama';
651
+ // Check for API key based on backend
652
+ let hasApiKey = false;
653
+ if (backend === 'gemini') {
654
+ hasApiKey = !!(secrets.geminiApiKey || process.env.GEMINI_API_KEY);
655
+ }
656
+ return {
657
+ backend,
658
+ hasApiKey,
659
+ ollamaUrl: process.env.OLLAMA_URL || 'http://localhost:11434',
660
+ ollamaConcurrency: config.embedding?.ollamaConcurrency || 1,
661
+ batchSize: config.indexing?.batchSize || DEFAULT_INDEXING.batchSize,
662
+ };
663
+ }
664
+ /**
665
+ * Save dashboard settings to .glancey.local.json (gitignored, user-specific)
666
+ */
667
+ export async function saveDashboardSettings(projectPath, settings) {
668
+ const localConfigPath = path.join(projectPath, LOCAL_CONFIG_FILENAME);
669
+ let localConfig = {};
670
+ try {
671
+ const content = await fs.readFile(localConfigPath, 'utf-8');
672
+ localConfig = JSON.parse(content);
673
+ }
674
+ catch {
675
+ // File doesn't exist, start fresh
676
+ }
677
+ // Update dashboard config
678
+ localConfig.dashboard = {
679
+ ...localConfig.dashboard,
680
+ enabled: settings.enabled,
681
+ };
682
+ // Only update port if provided
683
+ if (settings.port !== undefined) {
684
+ localConfig.dashboard.port = settings.port;
685
+ }
686
+ // Only update openBrowser if provided
687
+ if (settings.openBrowser !== undefined) {
688
+ localConfig.dashboard.openBrowser = settings.openBrowser;
689
+ }
690
+ await fs.writeFile(localConfigPath, JSON.stringify(localConfig, null, 2));
691
+ }
692
+ /**
693
+ * Get current dashboard settings
694
+ */
695
+ export async function getDashboardSettings(projectPath) {
696
+ const config = await loadConfig(projectPath);
697
+ return {
698
+ enabled: config.dashboard?.enabled ?? DEFAULT_DASHBOARD.enabled,
699
+ port: config.dashboard?.port ?? DEFAULT_DASHBOARD.port,
700
+ openBrowser: config.dashboard?.openBrowser ?? DEFAULT_DASHBOARD.openBrowser,
701
+ };
702
+ }
703
+ /**
704
+ * Save search weights to .glancey.local.json (gitignored, user-specific)
705
+ */
706
+ export async function saveSearchWeights(projectPath, settings) {
707
+ const localConfigPath = path.join(projectPath, LOCAL_CONFIG_FILENAME);
708
+ let localConfig = {};
709
+ try {
710
+ const content = await fs.readFile(localConfigPath, 'utf-8');
711
+ localConfig = JSON.parse(content);
712
+ }
713
+ catch {
714
+ // File doesn't exist, start fresh
715
+ }
716
+ // Update search config
717
+ localConfig.search = {
718
+ ...localConfig.search,
719
+ semanticWeight: settings.semanticWeight,
720
+ keywordWeight: settings.keywordWeight,
721
+ };
722
+ await fs.writeFile(localConfigPath, JSON.stringify(localConfig, null, 2));
723
+ }
724
+ /**
725
+ * Get current search weights
726
+ */
727
+ export async function getSearchWeights(projectPath) {
728
+ const config = await loadConfig(projectPath);
729
+ return {
730
+ semanticWeight: config.search?.semanticWeight ?? DEFAULT_SEARCH.semanticWeight,
731
+ keywordWeight: config.search?.keywordWeight ?? DEFAULT_SEARCH.keywordWeight,
732
+ };
733
+ }
734
+ /**
735
+ * Add a pattern to include or exclude patterns in .glancey.local.json
736
+ */
737
+ export async function addPattern(projectPath, pattern, type) {
738
+ const localConfigPath = path.join(projectPath, LOCAL_CONFIG_FILENAME);
739
+ let localConfig = {};
740
+ try {
741
+ const content = await fs.readFile(localConfigPath, 'utf-8');
742
+ localConfig = JSON.parse(content);
743
+ }
744
+ catch {
745
+ // File doesn't exist, start fresh
746
+ }
747
+ // Get effective config to check for duplicates and get current patterns
748
+ const effectiveConfig = await loadConfig(projectPath);
749
+ if (type === 'include') {
750
+ const currentPatterns = effectiveConfig.patterns || [];
751
+ if (!currentPatterns.includes(pattern)) {
752
+ localConfig.patterns = [...currentPatterns, pattern];
753
+ }
754
+ }
755
+ else {
756
+ const currentPatterns = effectiveConfig.excludePatterns || [];
757
+ if (!currentPatterns.includes(pattern)) {
758
+ localConfig.excludePatterns = [...currentPatterns, pattern];
759
+ }
760
+ }
761
+ await fs.writeFile(localConfigPath, JSON.stringify(localConfig, null, 2));
762
+ }
763
+ /**
764
+ * Remove a pattern from include or exclude patterns in .glancey.local.json
765
+ */
766
+ export async function removePattern(projectPath, pattern, type) {
767
+ const localConfigPath = path.join(projectPath, LOCAL_CONFIG_FILENAME);
768
+ let localConfig = {};
769
+ try {
770
+ const content = await fs.readFile(localConfigPath, 'utf-8');
771
+ localConfig = JSON.parse(content);
772
+ }
773
+ catch {
774
+ // File doesn't exist, start fresh
775
+ }
776
+ // Get effective config to get current patterns
777
+ const effectiveConfig = await loadConfig(projectPath);
778
+ if (type === 'include') {
779
+ const currentPatterns = effectiveConfig.patterns || [];
780
+ localConfig.patterns = currentPatterns.filter((p) => p !== pattern);
781
+ }
782
+ else {
783
+ const currentPatterns = effectiveConfig.excludePatterns || [];
784
+ localConfig.excludePatterns = currentPatterns.filter((p) => p !== pattern);
785
+ }
786
+ await fs.writeFile(localConfigPath, JSON.stringify(localConfig, null, 2));
787
+ }
788
+ //# sourceMappingURL=config.js.map