mdcontext 0.1.0 → 0.2.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 (251) hide show
  1. package/.changeset/config.json +9 -9
  2. package/.claude/settings.local.json +25 -0
  3. package/.github/workflows/claude-code-review.yml +44 -0
  4. package/.github/workflows/claude.yml +85 -0
  5. package/CONTRIBUTING.md +186 -0
  6. package/NOTES/NOTES +44 -0
  7. package/README.md +206 -3
  8. package/biome.json +1 -1
  9. package/dist/chunk-23UPXDNL.js +3044 -0
  10. package/dist/chunk-2W7MO2DL.js +1366 -0
  11. package/dist/chunk-3NUAZGMA.js +1689 -0
  12. package/dist/chunk-7TOWB2XB.js +366 -0
  13. package/dist/chunk-7XOTOADQ.js +3065 -0
  14. package/dist/chunk-AH2PDM2K.js +3042 -0
  15. package/dist/chunk-BNXWSZ63.js +3742 -0
  16. package/dist/chunk-BTL5DJVU.js +3222 -0
  17. package/dist/chunk-HDHYG7E4.js +104 -0
  18. package/dist/chunk-HLR4KZBP.js +3234 -0
  19. package/dist/chunk-IP3FRFEB.js +1045 -0
  20. package/dist/chunk-KHU56VDO.js +3042 -0
  21. package/dist/chunk-KRYIFLQR.js +85 -89
  22. package/dist/chunk-LBSDNLEM.js +287 -0
  23. package/dist/chunk-MNTQ7HCP.js +2643 -0
  24. package/dist/chunk-MUJELQQ6.js +1387 -0
  25. package/dist/chunk-MXJGMSLV.js +2199 -0
  26. package/dist/chunk-N6QJGC3Z.js +2636 -0
  27. package/dist/chunk-OBELGBPM.js +1713 -0
  28. package/dist/chunk-OT7R5XTA.js +3192 -0
  29. package/dist/chunk-P7X4RA2T.js +106 -0
  30. package/dist/chunk-PIDUQNC2.js +3185 -0
  31. package/dist/chunk-POGCDIH4.js +3187 -0
  32. package/dist/chunk-PSIEOQGZ.js +3043 -0
  33. package/dist/chunk-PVRT3IHA.js +3238 -0
  34. package/dist/chunk-QNN4TT23.js +1430 -0
  35. package/dist/chunk-RE3R45RJ.js +3042 -0
  36. package/dist/chunk-S7E6TFX6.js +718 -657
  37. package/dist/chunk-SG6GLU4U.js +1378 -0
  38. package/dist/chunk-SJCDV2ST.js +274 -0
  39. package/dist/chunk-SYE5XLF3.js +104 -0
  40. package/dist/chunk-T5VLYBZD.js +103 -0
  41. package/dist/chunk-TOQB7VWU.js +3238 -0
  42. package/dist/chunk-VFNMZ4ZQ.js +3228 -0
  43. package/dist/chunk-VVTGZNBT.js +1533 -1423
  44. package/dist/chunk-W7Q4RFEV.js +104 -0
  45. package/dist/chunk-XTYYVRLO.js +3190 -0
  46. package/dist/chunk-Y6MDYVJD.js +3063 -0
  47. package/dist/cli/main.js +4072 -629
  48. package/dist/index.d.ts +420 -33
  49. package/dist/index.js +8 -15
  50. package/dist/mcp/server.js +103 -7
  51. package/dist/schema-BAWSG7KY.js +22 -0
  52. package/dist/schema-E3QUPL26.js +20 -0
  53. package/dist/schema-EHL7WUT6.js +20 -0
  54. package/docs/019-USAGE.md +44 -5
  55. package/docs/020-current-implementation.md +8 -8
  56. package/docs/021-DOGFOODING-FINDINGS.md +1 -1
  57. package/docs/CONFIG.md +1123 -0
  58. package/docs/ERRORS.md +383 -0
  59. package/docs/summarization.md +320 -0
  60. package/justfile +40 -0
  61. package/package.json +39 -33
  62. package/research/INDEX.md +315 -0
  63. package/research/code-review/README.md +90 -0
  64. package/research/code-review/cli-error-handling-review.md +979 -0
  65. package/research/code-review/code-review-validation-report.md +464 -0
  66. package/research/code-review/main-ts-review.md +1128 -0
  67. package/research/config-docs/SUMMARY.md +357 -0
  68. package/research/config-docs/TEST-RESULTS.md +776 -0
  69. package/research/config-docs/TODO.md +542 -0
  70. package/research/config-docs/analysis.md +744 -0
  71. package/research/config-docs/fix-validation.md +502 -0
  72. package/research/config-docs/help-audit.md +264 -0
  73. package/research/config-docs/help-system-analysis.md +890 -0
  74. package/research/frontmatter/COMMENTS-ARE-SKIPPED.md +149 -0
  75. package/research/frontmatter/LLM-CODE-NAVIGATION.md +276 -0
  76. package/research/issue-review.md +603 -0
  77. package/research/llm-summarization/agent-cli-tools-2026.md +1082 -0
  78. package/research/llm-summarization/alternative-providers-2026.md +1428 -0
  79. package/research/llm-summarization/anthropic-2026.md +367 -0
  80. package/research/llm-summarization/claude-cli-integration.md +1706 -0
  81. package/research/llm-summarization/cli-integration-patterns.md +3155 -0
  82. package/research/llm-summarization/openai-2026.md +473 -0
  83. package/research/llm-summarization/openai-compatible-providers-2026.md +1022 -0
  84. package/research/llm-summarization/opencode-cli-integration.md +1552 -0
  85. package/research/llm-summarization/prompt-engineering-2026.md +1426 -0
  86. package/research/llm-summarization/prototype-results.md +56 -0
  87. package/research/llm-summarization/provider-switching-patterns-2026.md +2153 -0
  88. package/research/llm-summarization/typescript-llm-libraries-2026.md +2436 -0
  89. package/research/mdcontext-pudding/00-EXECUTIVE-SUMMARY.md +282 -0
  90. package/research/mdcontext-pudding/01-index-embed.md +956 -0
  91. package/research/mdcontext-pudding/02-search-COMMANDS.md +142 -0
  92. package/research/mdcontext-pudding/02-search-SUMMARY.md +146 -0
  93. package/research/mdcontext-pudding/02-search.md +970 -0
  94. package/research/mdcontext-pudding/03-context.md +779 -0
  95. package/research/mdcontext-pudding/04-navigation-and-analytics.md +803 -0
  96. package/research/mdcontext-pudding/04-tree.md +704 -0
  97. package/research/mdcontext-pudding/05-config.md +1038 -0
  98. package/research/mdcontext-pudding/06-links-summary.txt +87 -0
  99. package/research/mdcontext-pudding/06-links.md +679 -0
  100. package/research/mdcontext-pudding/07-stats.md +693 -0
  101. package/research/mdcontext-pudding/BUG-FIX-PLAN.md +388 -0
  102. package/research/mdcontext-pudding/P0-BUG-VALIDATION.md +167 -0
  103. package/research/mdcontext-pudding/README.md +168 -0
  104. package/research/mdcontext-pudding/TESTING-SUMMARY.md +128 -0
  105. package/research/research-quality-review.md +834 -0
  106. package/research/semantic-search/embedding-text-analysis.md +156 -0
  107. package/research/semantic-search/multi-word-failure-reproduction.md +171 -0
  108. package/research/semantic-search/query-processing-analysis.md +207 -0
  109. package/research/semantic-search/root-cause-and-solution.md +114 -0
  110. package/research/semantic-search/threshold-validation-report.md +69 -0
  111. package/research/semantic-search/vector-search-analysis.md +63 -0
  112. package/research/test-path-issues.md +276 -0
  113. package/review/ALP-76/1-error-type-design.md +962 -0
  114. package/review/ALP-76/2-error-handling-patterns.md +906 -0
  115. package/review/ALP-76/3-error-presentation.md +624 -0
  116. package/review/ALP-76/4-test-coverage.md +625 -0
  117. package/review/ALP-76/5-migration-completeness.md +440 -0
  118. package/review/ALP-76/6-effect-best-practices.md +755 -0
  119. package/scripts/apply-branch-protection.sh +47 -0
  120. package/scripts/branch-protection-templates.json +79 -0
  121. package/scripts/prototype-summarization.ts +346 -0
  122. package/scripts/rebuild-hnswlib.js +32 -37
  123. package/scripts/setup-branch-protection.sh +64 -0
  124. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/active-provider.json +7 -0
  125. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/bm25.json +541 -0
  126. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/bm25.meta.json +5 -0
  127. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/config.json +8 -0
  128. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/embeddings/openai_text-embedding-3-small_512/vectors.bin +0 -0
  129. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/embeddings/openai_text-embedding-3-small_512/vectors.meta.bin +0 -0
  130. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/indexes/documents.json +60 -0
  131. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/indexes/links.json +13 -0
  132. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/indexes/sections.json +1197 -0
  133. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/configuration-management.md +99 -0
  134. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/distributed-systems.md +92 -0
  135. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/error-handling.md +78 -0
  136. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/failure-automation.md +55 -0
  137. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/job-context.md +69 -0
  138. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/process-orchestration.md +99 -0
  139. package/src/cli/argv-preprocessor.test.ts +2 -2
  140. package/src/cli/cli.test.ts +230 -33
  141. package/src/cli/commands/config-cmd.ts +642 -0
  142. package/src/cli/commands/context.ts +97 -9
  143. package/src/cli/commands/duplicates.ts +122 -0
  144. package/src/cli/commands/embeddings.ts +529 -0
  145. package/src/cli/commands/index-cmd.ts +210 -30
  146. package/src/cli/commands/index.ts +3 -0
  147. package/src/cli/commands/search.ts +894 -64
  148. package/src/cli/commands/stats.ts +3 -0
  149. package/src/cli/commands/tree.ts +26 -5
  150. package/src/cli/config-layer.ts +176 -0
  151. package/src/cli/error-handler.test.ts +235 -0
  152. package/src/cli/error-handler.ts +655 -0
  153. package/src/cli/flag-schemas.ts +66 -0
  154. package/src/cli/help.ts +209 -7
  155. package/src/cli/main.ts +348 -58
  156. package/src/cli/options.ts +10 -0
  157. package/src/cli/shared-error-handling.ts +199 -0
  158. package/src/cli/utils.ts +150 -17
  159. package/src/config/file-provider.test.ts +320 -0
  160. package/src/config/file-provider.ts +273 -0
  161. package/src/config/index.ts +72 -0
  162. package/src/config/integration.test.ts +667 -0
  163. package/src/config/precedence.test.ts +277 -0
  164. package/src/config/precedence.ts +451 -0
  165. package/src/config/schema.test.ts +414 -0
  166. package/src/config/schema.ts +603 -0
  167. package/src/config/service.test.ts +320 -0
  168. package/src/config/service.ts +243 -0
  169. package/src/config/testing.test.ts +264 -0
  170. package/src/config/testing.ts +110 -0
  171. package/src/core/types.ts +6 -33
  172. package/src/duplicates/detector.test.ts +183 -0
  173. package/src/duplicates/detector.ts +414 -0
  174. package/src/duplicates/index.ts +18 -0
  175. package/src/embeddings/embedding-namespace.test.ts +300 -0
  176. package/src/embeddings/embedding-namespace.ts +947 -0
  177. package/src/embeddings/heading-boost.test.ts +222 -0
  178. package/src/embeddings/hnsw-build-options.test.ts +198 -0
  179. package/src/embeddings/hyde.test.ts +272 -0
  180. package/src/embeddings/hyde.ts +264 -0
  181. package/src/embeddings/index.ts +2 -0
  182. package/src/embeddings/openai-provider.ts +332 -83
  183. package/src/embeddings/pricing.json +22 -0
  184. package/src/embeddings/provider-constants.ts +204 -0
  185. package/src/embeddings/provider-errors.test.ts +967 -0
  186. package/src/embeddings/provider-errors.ts +565 -0
  187. package/src/embeddings/provider-factory.test.ts +240 -0
  188. package/src/embeddings/provider-factory.ts +225 -0
  189. package/src/embeddings/provider-integration.test.ts +788 -0
  190. package/src/embeddings/query-preprocessing.test.ts +187 -0
  191. package/src/embeddings/semantic-search-threshold.test.ts +508 -0
  192. package/src/embeddings/semantic-search.ts +780 -93
  193. package/src/embeddings/types.ts +293 -16
  194. package/src/embeddings/vector-store.ts +486 -77
  195. package/src/embeddings/voyage-provider.ts +313 -0
  196. package/src/errors/errors.test.ts +845 -0
  197. package/src/errors/index.ts +533 -0
  198. package/src/index/ignore-patterns.test.ts +354 -0
  199. package/src/index/ignore-patterns.ts +305 -0
  200. package/src/index/indexer.ts +286 -48
  201. package/src/index/storage.ts +94 -30
  202. package/src/index/types.ts +40 -2
  203. package/src/index/watcher.ts +67 -9
  204. package/src/index.ts +22 -0
  205. package/src/integration/search-keyword.test.ts +678 -0
  206. package/src/mcp/server.ts +135 -6
  207. package/src/parser/parser.ts +18 -19
  208. package/src/parser/section-filter.test.ts +277 -0
  209. package/src/parser/section-filter.ts +125 -3
  210. package/src/search/__tests__/hybrid-search.test.ts +650 -0
  211. package/src/search/bm25-store.ts +366 -0
  212. package/src/search/cross-encoder.test.ts +253 -0
  213. package/src/search/cross-encoder.ts +406 -0
  214. package/src/search/fuzzy-search.test.ts +419 -0
  215. package/src/search/fuzzy-search.ts +273 -0
  216. package/src/search/hybrid-search.ts +448 -0
  217. package/src/search/path-matcher.test.ts +276 -0
  218. package/src/search/path-matcher.ts +33 -0
  219. package/src/search/searcher.test.ts +99 -1
  220. package/src/search/searcher.ts +189 -67
  221. package/src/search/wink-bm25.d.ts +30 -0
  222. package/src/summarization/cli-providers/claude.ts +202 -0
  223. package/src/summarization/cli-providers/detection.test.ts +273 -0
  224. package/src/summarization/cli-providers/detection.ts +118 -0
  225. package/src/summarization/cli-providers/index.ts +8 -0
  226. package/src/summarization/cost.test.ts +139 -0
  227. package/src/summarization/cost.ts +102 -0
  228. package/src/summarization/error-handler.test.ts +127 -0
  229. package/src/summarization/error-handler.ts +111 -0
  230. package/src/summarization/index.ts +102 -0
  231. package/src/summarization/pipeline.test.ts +498 -0
  232. package/src/summarization/pipeline.ts +231 -0
  233. package/src/summarization/prompts.test.ts +269 -0
  234. package/src/summarization/prompts.ts +133 -0
  235. package/src/summarization/provider-factory.test.ts +396 -0
  236. package/src/summarization/provider-factory.ts +178 -0
  237. package/src/summarization/types.ts +184 -0
  238. package/src/summarize/summarizer.ts +104 -35
  239. package/src/types/huggingface-transformers.d.ts +66 -0
  240. package/tests/fixtures/cli/.mdcontext/active-provider.json +7 -0
  241. package/tests/fixtures/cli/.mdcontext/embeddings/openai_text-embedding-3-small_512/vectors.bin +0 -0
  242. package/tests/fixtures/cli/.mdcontext/embeddings/openai_text-embedding-3-small_512/vectors.meta.bin +0 -0
  243. package/tests/fixtures/cli/.mdcontext/indexes/documents.json +4 -4
  244. package/tests/fixtures/cli/.mdcontext/indexes/sections.json +14 -0
  245. package/tests/integration/embed-index.test.ts +712 -0
  246. package/tests/integration/search-context.test.ts +469 -0
  247. package/tests/integration/search-semantic.test.ts +522 -0
  248. package/vitest.config.ts +1 -6
  249. package/AGENTS.md +0 -46
  250. package/tests/fixtures/cli/.mdcontext/vectors.bin +0 -0
  251. package/tests/fixtures/cli/.mdcontext/vectors.meta.json +0 -1264
@@ -7,15 +7,24 @@
7
7
  import * as path from 'node:path'
8
8
  import * as readline from 'node:readline'
9
9
  import { Args, Command, Options } from '@effect/cli'
10
- import { Console, Effect } from 'effect'
11
- import { handleApiKeyError } from '../../embeddings/openai-provider.js'
10
+ import { Console, Effect, Option } from 'effect'
11
+ import type {
12
+ BuildEmbeddingsResult,
13
+ EmbeddingEstimate,
14
+ } from '../../embeddings/semantic-search.js'
12
15
  import {
13
16
  buildEmbeddings,
17
+ checkPricingFreshness,
14
18
  estimateEmbeddingCost,
19
+ getPricingDate,
15
20
  } from '../../embeddings/semantic-search.js'
16
21
  import { buildIndex } from '../../index/indexer.js'
17
22
  import { watchDirectory } from '../../index/watcher.js'
18
23
  import { forceOption, jsonOption, prettyOption } from '../options.js'
24
+ import {
25
+ createCostEstimateErrorHandler,
26
+ createEmbeddingErrorHandler,
27
+ } from '../shared-error-handling.js'
19
28
  import { formatJson, hasEmbeddings } from '../utils.js'
20
29
 
21
30
  const promptUser = (message: string): Promise<string> => {
@@ -50,7 +59,50 @@ export const indexCommand = Command.make(
50
59
  exclude: Options.text('exclude').pipe(
51
60
  Options.withAlias('x'),
52
61
  Options.withDescription(
53
- 'Exclude files matching patterns (comma-separated globs)',
62
+ 'Additional patterns to exclude (comma-separated). Patterns from .gitignore and .mdcontextignore are honored automatically.',
63
+ ),
64
+ Options.optional,
65
+ ),
66
+ noGitignore: Options.boolean('no-gitignore').pipe(
67
+ Options.withDescription('Ignore .gitignore file'),
68
+ Options.withDefault(false),
69
+ ),
70
+ provider: Options.choice('provider', [
71
+ 'openai',
72
+ 'ollama',
73
+ 'lm-studio',
74
+ 'openrouter',
75
+ 'voyage',
76
+ ]).pipe(
77
+ Options.withDescription(
78
+ 'Embedding provider: openai, ollama, lm-studio, openrouter, or voyage',
79
+ ),
80
+ Options.optional,
81
+ ),
82
+ providerBaseUrl: Options.text('provider-base-url').pipe(
83
+ Options.withDescription('Custom provider API base URL'),
84
+ Options.optional,
85
+ ),
86
+ providerModel: Options.text('provider-model').pipe(
87
+ Options.withDescription('Embedding model to use'),
88
+ Options.optional,
89
+ ),
90
+ hnswM: Options.integer('hnsw-m').pipe(
91
+ Options.withDescription(
92
+ 'HNSW M parameter: max connections per node. Higher = better recall, larger index. Recommended: 12 (speed), 16 (balanced, default), 24 (quality)',
93
+ ),
94
+ Options.optional,
95
+ ),
96
+ hnswEfConstruction: Options.integer('hnsw-ef-construction').pipe(
97
+ Options.withDescription(
98
+ 'HNSW efConstruction: construction-time search width. Higher = better quality, slower builds. Recommended: 128 (speed), 200 (balanced, default), 256 (quality)',
99
+ ),
100
+ Options.optional,
101
+ ),
102
+ timeout: Options.integer('timeout').pipe(
103
+ Options.withAlias('t'),
104
+ Options.withDescription(
105
+ 'Request timeout in milliseconds for embedding API calls (default: 30000)',
54
106
  ),
55
107
  Options.optional,
56
108
  ),
@@ -68,6 +120,13 @@ export const indexCommand = Command.make(
68
120
  embed,
69
121
  noEmbed,
70
122
  exclude,
123
+ noGitignore,
124
+ provider,
125
+ providerBaseUrl,
126
+ providerModel,
127
+ hnswM,
128
+ hnswEfConstruction,
129
+ timeout,
71
130
  watch: watchMode,
72
131
  force,
73
132
  json,
@@ -76,8 +135,9 @@ export const indexCommand = Command.make(
76
135
  Effect.gen(function* () {
77
136
  const resolvedDir = path.resolve(dirPath)
78
137
 
79
- // Parse exclude patterns
80
- const excludePatterns =
138
+ // Parse exclude patterns - CLI adds to ignore files
139
+ // Note: buildIndex now honors .gitignore and .mdcontextignore by default
140
+ const cliExcludePatterns =
81
141
  exclude._tag === 'Some'
82
142
  ? exclude.value.split(',').map((p) => p.trim())
83
143
  : undefined
@@ -89,6 +149,8 @@ export const indexCommand = Command.make(
89
149
 
90
150
  const watcher = yield* watchDirectory(resolvedDir, {
91
151
  force,
152
+ exclude: cliExcludePatterns,
153
+ honorGitignore: !noGitignore,
92
154
  onIndex: (result) => {
93
155
  if (json) {
94
156
  console.log(formatJson(result, pretty))
@@ -113,7 +175,22 @@ export const indexCommand = Command.make(
113
175
  } else {
114
176
  yield* Console.log(`Indexing ${resolvedDir}...`)
115
177
 
116
- const result = yield* buildIndex(resolvedDir, { force })
178
+ const result = yield* buildIndex(resolvedDir, {
179
+ force,
180
+ exclude: cliExcludePatterns,
181
+ honorGitignore: !noGitignore,
182
+ onProgress: (progress) => {
183
+ if (!json) {
184
+ const progressMsg = ` [${progress.current}/${progress.total}] ${progress.filePath}`
185
+ process.stdout.write(`\x1b[2K\r${progressMsg}`)
186
+ }
187
+ },
188
+ })
189
+
190
+ // Clear the progress line after indexing completes
191
+ if (!json) {
192
+ process.stdout.write('\x1b[2K\r')
193
+ }
117
194
 
118
195
  if (!json) {
119
196
  yield* Console.log('')
@@ -129,6 +206,21 @@ export const indexCommand = Command.make(
129
206
  yield* Console.log(` Links: ${result.totalLinks}`)
130
207
  yield* Console.log(` Duration: ${result.duration}ms`)
131
208
 
209
+ // Show skip summary if any files were skipped
210
+ if (result.skipped.total > 0) {
211
+ const skipParts: string[] = []
212
+ if (result.skipped.unchanged > 0) {
213
+ skipParts.push(`${result.skipped.unchanged} unchanged`)
214
+ }
215
+ if (result.skipped.hidden > 0) {
216
+ skipParts.push(`${result.skipped.hidden} hidden`)
217
+ }
218
+ if (result.skipped.excluded > 0) {
219
+ skipParts.push(`${result.skipped.excluded} excluded`)
220
+ }
221
+ yield* Console.log(` Skipped: ${skipParts.join(', ')}`)
222
+ }
223
+
132
224
  if (result.errors.length > 0) {
133
225
  yield* Console.log('')
134
226
  yield* Console.log(`Errors (${result.errors.length}):`)
@@ -147,10 +239,10 @@ export const indexCommand = Command.make(
147
239
  if (embed) {
148
240
  yield* Console.log('')
149
241
 
150
- // Show cost estimate first
242
+ // Show cost estimate first - errors propagate to CLI boundary
151
243
  const estimate = yield* estimateEmbeddingCost(resolvedDir, {
152
- excludePatterns,
153
- }).pipe(handleApiKeyError)
244
+ excludePatterns: cliExcludePatterns,
245
+ })
154
246
 
155
247
  if (!json) {
156
248
  yield* Console.log(`Found ${estimate.totalFiles} files to embed:`)
@@ -165,8 +257,14 @@ export const indexCommand = Command.make(
165
257
  }
166
258
  yield* Console.log('')
167
259
  yield* Console.log(
168
- `Total: ~${estimate.totalTokens.toLocaleString()} tokens, ~$${estimate.totalCost.toFixed(4)}, ~${estimate.estimatedTimeSeconds}s`,
260
+ `Total: ~${estimate.totalTokens.toLocaleString()} tokens, ~$${estimate.totalCost.toFixed(4)} (pricing as of ${getPricingDate()}), ~${estimate.estimatedTimeSeconds}s`,
169
261
  )
262
+
263
+ // Check for stale pricing data
264
+ const stalenessWarning = checkPricingFreshness()
265
+ if (stalenessWarning) {
266
+ yield* Console.log(` Warning: ${stalenessWarning}`)
267
+ }
170
268
  yield* Console.log('')
171
269
  }
172
270
 
@@ -176,21 +274,56 @@ export const indexCommand = Command.make(
176
274
  yield* Console.log('Rebuilding embeddings (--force specified)...')
177
275
  }
178
276
 
277
+ // Build provider config from CLI flags if specified
278
+ const cliTimeout = Option.getOrUndefined(timeout)
279
+ const providerConfig = Option.isSome(provider)
280
+ ? {
281
+ provider: provider.value as
282
+ | 'openai'
283
+ | 'ollama'
284
+ | 'lm-studio'
285
+ | 'openrouter'
286
+ | 'voyage',
287
+ baseURL: Option.getOrUndefined(providerBaseUrl),
288
+ model: Option.getOrUndefined(providerModel),
289
+ timeout: cliTimeout,
290
+ }
291
+ : cliTimeout !== undefined
292
+ ? { provider: 'openai' as const, timeout: cliTimeout }
293
+ : undefined
294
+
295
+ // Build HNSW options from CLI flags if specified
296
+ const hnswOptions =
297
+ Option.isSome(hnswM) || Option.isSome(hnswEfConstruction)
298
+ ? {
299
+ m: Option.getOrUndefined(hnswM),
300
+ efConstruction: Option.getOrUndefined(hnswEfConstruction),
301
+ }
302
+ : undefined
303
+
304
+ // Build embeddings - errors propagate to CLI boundary
179
305
  const embedResult = yield* buildEmbeddings(resolvedDir, {
180
306
  force,
181
- excludePatterns,
307
+ excludePatterns: cliExcludePatterns,
308
+ providerConfig,
309
+ hnswOptions,
182
310
  onFileProgress: (progress) => {
183
311
  if (!json) {
184
- process.stdout.write(
185
- `\r [${progress.fileIndex}/${progress.totalFiles}] ${progress.filePath} (${progress.sectionCount} sections)...`,
186
- )
312
+ const progressMsg = ` [${progress.fileIndex}/${progress.totalFiles}] ${progress.filePath} (${progress.sectionCount} sections)...`
313
+ process.stdout.write(`\x1b[2K\r${progressMsg}`)
187
314
  }
188
315
  },
189
- }).pipe(handleApiKeyError)
316
+ onBatchProgress: (progress) => {
317
+ if (!json) {
318
+ const progressMsg = ` Embedding [${progress.processedSections}/${progress.totalSections}] sections (batch ${progress.batchIndex}/${progress.totalBatches})...`
319
+ process.stdout.write(`\x1b[2K\r${progressMsg}`)
320
+ }
321
+ },
322
+ })
190
323
 
191
324
  if (!json) {
192
- // Clear the progress line
193
- process.stdout.write(`\r${' '.repeat(80)}\r`)
325
+ // Clear the progress line completely
326
+ process.stdout.write(`\r${' '.repeat(120)}\r`)
194
327
  yield* Console.log('')
195
328
 
196
329
  if (embedResult.cacheHit) {
@@ -228,8 +361,11 @@ export const indexCommand = Command.make(
228
361
  yield* Console.log('')
229
362
 
230
363
  // Get cost estimate for the prompt
364
+ // Note: We gracefully handle errors here since this is optional information
365
+ // for the user prompt. IndexNotFoundError is expected if index doesn't exist.
231
366
  const estimate = yield* estimateEmbeddingCost(resolvedDir).pipe(
232
- Effect.catchAll(() => Effect.succeed(null)),
367
+ Effect.map((r): EmbeddingEstimate | null => r),
368
+ Effect.catchTags(createCostEstimateErrorHandler()),
233
369
  )
234
370
 
235
371
  if (estimate) {
@@ -237,7 +373,21 @@ export const indexCommand = Command.make(
237
373
  `Cost: ~$${estimate.totalCost.toFixed(4)} for this corpus (~${estimate.estimatedTimeSeconds}s)`,
238
374
  )
239
375
  }
240
- yield* Console.log('Requires: OPENAI_API_KEY environment variable')
376
+ yield* Console.log('Requires an embedding provider. Options:')
377
+ yield* Console.log(
378
+ ' - OpenAI (cloud): Set OPENAI_API_KEY environment variable',
379
+ )
380
+ yield* Console.log(
381
+ ' - Ollama (free, local): Run "ollama serve" - no API key needed',
382
+ )
383
+ yield* Console.log(
384
+ ' - LM Studio (free, local): Start the server - no API key needed',
385
+ )
386
+ yield* Console.log(
387
+ ' - OpenRouter (cloud): Set OPENROUTER_API_KEY environment variable',
388
+ )
389
+ yield* Console.log('')
390
+ yield* Console.log('See CONFIG.md for detailed setup instructions.')
241
391
  yield* Console.log('')
242
392
 
243
393
  const answer = yield* Effect.promise(() =>
@@ -245,36 +395,66 @@ export const indexCommand = Command.make(
245
395
  )
246
396
 
247
397
  if (answer === 'y' || answer === 'yes') {
248
- // Check for API key
398
+ // Check for API key (only required for cloud providers)
399
+ // Note: When no provider is configured, we default to OpenAI which needs a key
400
+ // Local providers (Ollama, LM Studio) don't need API keys
249
401
  if (!process.env.OPENAI_API_KEY) {
250
402
  yield* Console.log('')
251
- yield* Console.log('OPENAI_API_KEY not set.')
403
+ yield* Console.log('No embedding provider configured.')
404
+ yield* Console.log('')
405
+ yield* Console.log('Choose a provider:')
252
406
  yield* Console.log('')
407
+ yield* Console.log(' Cloud (requires API key):')
408
+ yield* Console.log(' export OPENAI_API_KEY=sk-...')
409
+ yield* Console.log(' export OPENROUTER_API_KEY=sk-...')
410
+ yield* Console.log('')
411
+ yield* Console.log(' Local (free, no API key needed):')
253
412
  yield* Console.log(
254
- 'To enable semantic search, set your OpenAI API key:',
413
+ ' Ollama: ollama serve && ollama pull nomic-embed-text',
255
414
  )
256
- yield* Console.log(' export OPENAI_API_KEY=sk-...')
415
+ yield* Console.log(' LM Studio: Start the server GUI')
257
416
  yield* Console.log('')
258
- yield* Console.log('Or add to .env file in project root.')
417
+ yield* Console.log(
418
+ 'Then run: mdcontext index --embed [--provider <name>]',
419
+ )
420
+ yield* Console.log('See CONFIG.md for detailed setup.')
259
421
  } else {
260
422
  yield* Console.log('')
261
423
  yield* Console.log('Building embeddings...')
262
424
 
425
+ // Build HNSW options from CLI flags if specified
426
+ const hnswOptionsPrompt =
427
+ Option.isSome(hnswM) || Option.isSome(hnswEfConstruction)
428
+ ? {
429
+ m: Option.getOrUndefined(hnswM),
430
+ efConstruction: Option.getOrUndefined(hnswEfConstruction),
431
+ }
432
+ : undefined
433
+
434
+ // Build provider config if timeout specified
435
+ const promptTimeout = Option.getOrUndefined(timeout)
436
+ const providerConfigPrompt =
437
+ promptTimeout !== undefined
438
+ ? { provider: 'openai' as const, timeout: promptTimeout }
439
+ : undefined
440
+
441
+ // Note: We gracefully handle errors here since embedding failure
442
+ // shouldn't block the main index operation. Errors are logged for debugging.
263
443
  const embedResult = yield* buildEmbeddings(resolvedDir, {
264
444
  force: false,
445
+ hnswOptions: hnswOptionsPrompt,
446
+ providerConfig: providerConfigPrompt,
265
447
  onFileProgress: (progress) => {
266
- process.stdout.write(
267
- `\r [${progress.fileIndex}/${progress.totalFiles}] ${progress.filePath} (${progress.sectionCount} sections)...`,
448
+ console.log(
449
+ ` [${progress.fileIndex}/${progress.totalFiles}] ${progress.filePath}`,
268
450
  )
269
451
  },
270
452
  }).pipe(
271
- handleApiKeyError,
272
- Effect.catchAll(() => Effect.succeed(null)),
453
+ Effect.map((r): BuildEmbeddingsResult | null => r),
454
+ Effect.catchTags(createEmbeddingErrorHandler()),
273
455
  )
274
456
 
275
457
  if (embedResult) {
276
- // Clear the progress line
277
- process.stdout.write(`\r${' '.repeat(80)}\r`)
278
458
  yield* Console.log('')
279
459
  yield* Console.log(
280
460
  `Completed in ${(embedResult.duration / 1000).toFixed(1)}s`,
@@ -5,7 +5,10 @@
5
5
  */
6
6
 
7
7
  export { backlinksCommand } from './backlinks.js'
8
+ export { configCommand } from './config-cmd.js'
8
9
  export { contextCommand } from './context.js'
10
+ export { duplicatesCommand } from './duplicates.js'
11
+ export { embeddingsCommand } from './embeddings.js'
9
12
  export { indexCommand } from './index-cmd.js'
10
13
  export { linksCommand } from './links.js'
11
14
  export { searchCommand } from './search.js'