claude-eidetic 0.1.0 → 0.1.2

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 (96) hide show
  1. package/README.md +333 -0
  2. package/dist/config.d.ts +25 -0
  3. package/dist/config.js +29 -14
  4. package/dist/core/cleanup.d.ts +8 -0
  5. package/dist/core/cleanup.js +41 -0
  6. package/dist/core/doc-indexer.d.ts +13 -0
  7. package/dist/core/doc-indexer.js +76 -0
  8. package/dist/core/doc-searcher.d.ts +13 -0
  9. package/dist/core/doc-searcher.js +65 -0
  10. package/dist/core/file-category.d.ts +7 -0
  11. package/dist/core/file-category.js +75 -0
  12. package/dist/core/indexer.js +12 -4
  13. package/dist/core/preview.d.ts +1 -2
  14. package/dist/core/preview.js +2 -5
  15. package/dist/core/repo-map.d.ts +33 -0
  16. package/dist/core/repo-map.js +144 -0
  17. package/dist/core/searcher.d.ts +1 -13
  18. package/dist/core/searcher.js +20 -24
  19. package/dist/core/snapshot-io.js +2 -2
  20. package/dist/core/sync.d.ts +5 -25
  21. package/dist/core/sync.js +90 -65
  22. package/dist/core/targeted-indexer.d.ts +19 -0
  23. package/dist/core/targeted-indexer.js +127 -0
  24. package/dist/embedding/factory.d.ts +0 -13
  25. package/dist/embedding/factory.js +0 -17
  26. package/dist/embedding/openai.d.ts +2 -14
  27. package/dist/embedding/openai.js +7 -20
  28. package/dist/errors.d.ts +2 -0
  29. package/dist/errors.js +2 -0
  30. package/dist/format.d.ts +12 -0
  31. package/dist/format.js +160 -31
  32. package/dist/hooks/post-tool-use.d.ts +13 -0
  33. package/dist/hooks/post-tool-use.js +113 -0
  34. package/dist/hooks/stop-hook.d.ts +11 -0
  35. package/dist/hooks/stop-hook.js +121 -0
  36. package/dist/hooks/targeted-runner.d.ts +11 -0
  37. package/dist/hooks/targeted-runner.js +66 -0
  38. package/dist/index.js +102 -24
  39. package/dist/infra/qdrant-bootstrap.js +14 -12
  40. package/dist/memory/history.d.ts +19 -0
  41. package/dist/memory/history.js +40 -0
  42. package/dist/memory/llm.d.ts +2 -0
  43. package/dist/memory/llm.js +56 -0
  44. package/dist/memory/prompts.d.ts +5 -0
  45. package/dist/memory/prompts.js +36 -0
  46. package/dist/memory/reconciler.d.ts +12 -0
  47. package/dist/memory/reconciler.js +36 -0
  48. package/dist/memory/store.d.ts +20 -0
  49. package/dist/memory/store.js +206 -0
  50. package/dist/memory/types.d.ts +28 -0
  51. package/dist/memory/types.js +2 -0
  52. package/dist/paths.d.ts +3 -4
  53. package/dist/paths.js +14 -4
  54. package/dist/precompact/hook.d.ts +9 -0
  55. package/dist/precompact/hook.js +170 -0
  56. package/dist/precompact/index-runner.d.ts +9 -0
  57. package/dist/precompact/index-runner.js +52 -0
  58. package/dist/precompact/note-writer.d.ts +15 -0
  59. package/dist/precompact/note-writer.js +109 -0
  60. package/dist/precompact/session-indexer.d.ts +13 -0
  61. package/dist/precompact/session-indexer.js +31 -0
  62. package/dist/precompact/tier0-inject.d.ts +16 -0
  63. package/dist/precompact/tier0-inject.js +88 -0
  64. package/dist/precompact/tier0-writer.d.ts +16 -0
  65. package/dist/precompact/tier0-writer.js +74 -0
  66. package/dist/precompact/transcript-parser.d.ts +10 -0
  67. package/dist/precompact/transcript-parser.js +148 -0
  68. package/dist/precompact/types.d.ts +93 -0
  69. package/dist/precompact/types.js +5 -0
  70. package/dist/precompact/utils.d.ts +29 -0
  71. package/dist/precompact/utils.js +95 -0
  72. package/dist/setup-message.d.ts +3 -0
  73. package/dist/setup-message.js +42 -0
  74. package/dist/splitter/ast.js +84 -22
  75. package/dist/splitter/line.d.ts +0 -4
  76. package/dist/splitter/line.js +1 -7
  77. package/dist/splitter/symbol-extract.d.ts +16 -0
  78. package/dist/splitter/symbol-extract.js +61 -0
  79. package/dist/splitter/types.d.ts +5 -0
  80. package/dist/splitter/types.js +1 -1
  81. package/dist/state/doc-metadata.d.ts +18 -0
  82. package/dist/state/doc-metadata.js +59 -0
  83. package/dist/state/registry.d.ts +1 -3
  84. package/dist/state/snapshot.d.ts +0 -1
  85. package/dist/state/snapshot.js +3 -19
  86. package/dist/tool-schemas.d.ts +251 -1
  87. package/dist/tool-schemas.js +307 -0
  88. package/dist/tools.d.ts +69 -0
  89. package/dist/tools.js +286 -17
  90. package/dist/vectordb/milvus.d.ts +7 -5
  91. package/dist/vectordb/milvus.js +116 -19
  92. package/dist/vectordb/qdrant.d.ts +8 -10
  93. package/dist/vectordb/qdrant.js +105 -33
  94. package/dist/vectordb/types.d.ts +20 -0
  95. package/messages.yaml +50 -0
  96. package/package.json +31 -6
package/README.md ADDED
@@ -0,0 +1,333 @@
1
+ # claude-eidetic
2
+
3
+
4
+ [![tests](https://img.shields.io/github/actions/workflow/status/eidetics/claude-eidetic/ci.yml?style=flat-square&label=tests)](https://github.com/eidetics/claude-eidetic/actions/workflows/ci.yml)
5
+ [![npm](https://img.shields.io/npm/v/claude-eidetic)](https://www.npmjs.com/package/claude-eidetic)
6
+ [![MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
7
+
8
+ Semantic code search, persistent memory, and session continuity for Claude Code. One plugin, not three.
9
+
10
+ ---
11
+
12
+ ## 🚀 Quick Start
13
+
14
+ ```bash
15
+ claude plugin install eidetics/claude-eidetic
16
+ ```
17
+
18
+ ```bash
19
+ export OPENAI_API_KEY=sk-... # for embeddings (default)
20
+ export ANTHROPIC_API_KEY=sk-ant-... # for memory extraction (default)
21
+ ```
22
+
23
+ Index your codebase once, then search by meaning:
24
+
25
+ ```
26
+ index_codebase(path="/your/project")
27
+ search_code("how does authentication work")
28
+ ```
29
+
30
+ ---
31
+
32
+ ## ✨ Features
33
+
34
+ ### 🔍 Semantic Code Search
35
+
36
+ **Find code by meaning, not keywords.** Search across your entire codebase with natural language, returning the most relevant functions and classes, not a list of files to read.
37
+
38
+ ```
39
+ search_code("how does the retry logic work")
40
+ search_code("authentication middleware", extensionFilter=[".ts"])
41
+ search_code(project="backend", query="auth flow")
42
+ ```
43
+
44
+ ### 🏗️ Architecture at a Glance
45
+
46
+ **Get every class, function, and method in one call.** `browse_structure` returns a condensed map of your codebase with signatures, grouped by file, replacing a Glob + Read cascade with a single tool call.
47
+
48
+ ```
49
+ browse_structure(path="/my/project", kind="class")
50
+ list_symbols(path="/my/project", nameFilter="handle")
51
+ ```
52
+
53
+ ### 📚 Documentation Cache
54
+
55
+ **Fetch docs once, search them forever.** Cache external documentation as searchable embeddings. Retrieve relevant passages instantly, far cheaper than re-fetching the same page each session.
56
+
57
+ ```
58
+ index_document(content=..., library="react", topic="hooks", source="https://...")
59
+ search_documents("React useCallback dependencies", library="react")
60
+ ```
61
+
62
+ ### 🧠 Persistent Memory
63
+
64
+ **Claude remembers your preferences between sessions.** `add_memory` uses an LLM to extract structured facts from conversation text (coding style, architecture decisions, debugging insights) and deduplicates them semantically. Not a static config file you forget to update.
65
+
66
+ ```
67
+ add_memory("Always use absolute imports, never relative")
68
+ search_memory("how does this team handle errors")
69
+ ```
70
+
71
+ ### 🔄 Session Continuity
72
+
73
+ **Every session picks up where the last one left off.** When a session ends (or context compacts mid-session), Eidetic automatically writes a structured note capturing files changed, tasks, commands, and decisions. `/catchup` at the start of a new session reconstructs exactly where you were. No user action required.
74
+
75
+ ### 👻 Invisible Optimizations
76
+
77
+ Eight hook events fire automatically, nudging toward cheaper tools, redirecting file reads for 15-20% token savings, tracking changed files, and saving session state on exit.
78
+
79
+ <details>
80
+ <summary><strong>Hook event details</strong></summary>
81
+
82
+ | Hook | Trigger | What it does |
83
+ |---|---|---|
84
+ | `SessionStart` | Session opens | Validates config, injects last-session context |
85
+ | `UserPromptSubmit` | Every message | Nudges toward `search_code` over Grep/Explore for conceptual queries |
86
+ | `PreToolUse` (Read) | Before every Read | Blocks Read for text files, redirects to `read_file` for 15-20% token savings |
87
+ | `PreToolUse` (WebFetch / query-docs) | Before doc fetches | Suggests `search_documents` if library is cached (allows fetch either way) |
88
+ | `PostToolUse` (Write / Edit) | After every file write | Tracks changed files in a shadow git index |
89
+ | `Stop` | After Claude responds | Commits shadow index; triggers targeted re-index of changed files only |
90
+ | `PreCompact` | Before context compaction | Captures session state to notes before memory is lost |
91
+ | `SessionEnd` | Session closes | Writes session note (files, tasks, commands); extracts developer memories via LLM |
92
+
93
+ </details>
94
+
95
+ ---
96
+
97
+ ## 🗺️ When to Use What
98
+
99
+ | Need | Use | Notes |
100
+ |---|---|---|
101
+ | Find implementations by concept | `search_code` | Semantic, natural language queries |
102
+ | Exact string or regex match | Grep | Grep wins for exact matches |
103
+ | Find file by exact name | Glob | Glob wins for name patterns |
104
+ | Understand module structure | `browse_structure` | One call vs Glob + Read cascade |
105
+ | Read a specific known file | `read_file` | Cheaper than built-in Read for code files |
106
+ | Search cached documentation | `search_documents` | Far cheaper than re-fetching |
107
+ | Recall project conventions | `search_memory` | Global across all projects and sessions |
108
+
109
+ ---
110
+
111
+ ## 📖 Skills Reference
112
+
113
+ | Skill | What it does |
114
+ |---|---|
115
+ | `/search` | Guided semantic search with best-practice prompts |
116
+ | `/index` | Index or re-index a codebase with dry-run option |
117
+ | `/cache-docs` | Fetch and cache external documentation |
118
+ | `/catchup` | Search session notes and reconstruct where you left off |
119
+ | `/wrapup` | Extract decisions, rationale, open questions, and next actions from the conversation |
120
+
121
+ ---
122
+
123
+ <details>
124
+ <summary><strong>🔥 Why does this exist? (The Problem)</strong></summary>
125
+
126
+ Every new Claude Code session starts cold. You re-explain the architecture. You re-fetch the same docs. Claude reads the same files repeatedly, burning tokens just to get back to where you were.
127
+
128
+ | Task | Without Eidetic | With Eidetic |
129
+ |---|---|---|
130
+ | Find where auth errors are handled | Grep cascade, read 8 files, ~10,700 tokens | `search_code("auth error handling")` ~220 tokens |
131
+ | Resume after context compaction | Re-explain 20 min of context, ~2,000 tokens | `/catchup` ~200 tokens |
132
+ | Look up React hooks docs | Fetch docs page, ~5,000 tokens | `search_documents("React useEffect")` ~20 tokens |
133
+ | Read a 400-line file | Built-in Read with line numbers, ~900 tokens | `read_file(path)` ~740 tokens |
134
+
135
+ </details>
136
+
137
+ ---
138
+
139
+ ## 📦 Installation
140
+
141
+ ### Plugin (recommended)
142
+
143
+ ```bash
144
+ claude plugin install eidetics/claude-eidetic
145
+ ```
146
+
147
+ The plugin auto-starts the MCP server, installs skills, and configures hooks.
148
+
149
+ ### npx (manual MCP config)
150
+
151
+ Add to your `.mcp.json`:
152
+
153
+ ```json
154
+ {
155
+ "mcpServers": {
156
+ "claude-eidetic": {
157
+ "command": "npx",
158
+ "args": ["-y", "claude-eidetic"],
159
+ "env": {
160
+ "OPENAI_API_KEY": "sk-...",
161
+ "ANTHROPIC_API_KEY": "sk-ant-..."
162
+ }
163
+ }
164
+ }
165
+ }
166
+ ```
167
+
168
+ `ANTHROPIC_API_KEY` is needed for the memory LLM (default provider). Omit it if using `MEMORY_LLM_PROVIDER=openai` or `ollama`.
169
+
170
+ ### Global install
171
+
172
+ ```bash
173
+ npm install -g claude-eidetic
174
+ ```
175
+
176
+ ### From source
177
+
178
+ ```bash
179
+ git clone https://github.com/eidetics/claude-eidetic
180
+ cd claude-eidetic
181
+ npm install && npx tsc && npm start
182
+ ```
183
+
184
+ ### Requirements
185
+
186
+ - Node.js >= 20.0.0
187
+ - An API key (OpenAI for embeddings, Anthropic for memory extraction, or Ollama for both free)
188
+ - Docker (optional): Qdrant auto-provisions via Docker if not already running
189
+ - C/C++ build tools: required by tree-sitter native bindings (`node-gyp`)
190
+
191
+ ---
192
+
193
+ ## ⚙️ Configuration
194
+
195
+ All configuration is via environment variables. No config files.
196
+
197
+ ### Using Ollama (free, local)
198
+
199
+ ```bash
200
+ export EMBEDDING_PROVIDER=ollama
201
+ export MEMORY_LLM_PROVIDER=ollama
202
+ # No API keys needed
203
+ ```
204
+
205
+ <details>
206
+ <summary><strong>Full configuration reference</strong></summary>
207
+
208
+ | Variable | Default | Description |
209
+ |---|---|---|
210
+ | `OPENAI_API_KEY` | _(required for openai)_ | OpenAI API key for embeddings and/or memory |
211
+ | `ANTHROPIC_API_KEY` | _(required for anthropic memory)_ | Anthropic API key for memory LLM |
212
+ | `EMBEDDING_PROVIDER` | `openai` | `openai`, `ollama`, or `local` |
213
+ | `EMBEDDING_MODEL` | `text-embedding-3-small` (openai) / `nomic-embed-text` (ollama) | Embedding model name |
214
+ | `EMBEDDING_BATCH_SIZE` | `100` | Batch size for embedding requests (1-2048) |
215
+ | `INDEXING_CONCURRENCY` | `8` | Parallel file indexing workers (1-32) |
216
+ | `OPENAI_BASE_URL` | _(none)_ | Custom OpenAI-compatible endpoint |
217
+ | `OLLAMA_BASE_URL` | `http://localhost:11434/v1` | Ollama server URL |
218
+ | `VECTORDB_PROVIDER` | `qdrant` | `qdrant` or `milvus` |
219
+ | `QDRANT_URL` | `http://localhost:6333` | Qdrant server URL |
220
+ | `QDRANT_API_KEY` | _(none)_ | Qdrant API key (for remote/cloud instances) |
221
+ | `MILVUS_ADDRESS` | `localhost:19530` | Milvus server address |
222
+ | `MILVUS_TOKEN` | _(none)_ | Milvus authentication token |
223
+ | `EIDETIC_DATA_DIR` | `~/.eidetic/` | Data root for snapshots, memory DB, registry |
224
+ | `CUSTOM_EXTENSIONS` | `[]` | JSON array of extra file extensions to index (e.g., `[".dart",".arb"]`) |
225
+ | `CUSTOM_IGNORE_PATTERNS` | `[]` | JSON array of glob patterns to exclude |
226
+ | `MEMORY_LLM_PROVIDER` | `anthropic` | `anthropic`, `openai`, or `ollama` |
227
+ | `MEMORY_LLM_MODEL` | `claude-haiku-4-5-20251001` (anthropic) / `gpt-4o-mini` (openai) / `llama3.2` (ollama) | Model for memory extraction |
228
+ | `MEMORY_LLM_BASE_URL` | _(none)_ | Custom base URL for memory LLM |
229
+ | `MEMORY_LLM_API_KEY` | _(none)_ | API key override for memory LLM |
230
+
231
+ </details>
232
+
233
+ ---
234
+
235
+ <details>
236
+ <summary><strong>🔧 Tool Reference</strong></summary>
237
+
238
+ ### 🔍 Code Search
239
+
240
+ | Tool | Description |
241
+ |---|---|
242
+ | `search_code` | Hybrid semantic search over indexed codebase. Returns compact results by default. |
243
+ | `index_codebase` | Index a directory. Supports `dryRun`, `force`, `customExtensions`, `customIgnorePatterns`. |
244
+ | `list_indexed` | List all indexed codebases with file/chunk counts and status. |
245
+ | `get_indexing_status` | Check indexing progress for a path or project. |
246
+ | `clear_index` | Remove the search index for a codebase. |
247
+ | `cleanup_vectors` | Remove orphaned vectors for deleted files. No re-embedding cost. |
248
+ | `browse_structure` | Condensed structural map: classes, functions, methods with signatures, grouped by file. |
249
+ | `list_symbols` | Compact symbol table with name/kind/file/line. Supports name, kind, and path filters. |
250
+
251
+ ### 📄 File Reading
252
+
253
+ | Tool | Description |
254
+ |---|---|
255
+ | `read_file` | Read file without line-number overhead. Cheaper than built-in Read for code files. |
256
+
257
+ ### 📚 Documentation Cache
258
+
259
+ | Tool | Description |
260
+ |---|---|
261
+ | `index_document` | Cache external documentation for semantic search. Supports TTL for staleness tracking. |
262
+ | `search_documents` | Search cached docs. Far cheaper than re-fetching the same page. |
263
+
264
+ ### 🧠 Memory
265
+
266
+ | Tool | Description |
267
+ |---|---|
268
+ | `add_memory` | LLM-extracted facts from text. Auto-deduplicates. Seven categories. |
269
+ | `search_memory` | Semantic search over stored memories. Filterable by category. |
270
+ | `list_memories` | List all memories, optionally filtered by category. |
271
+ | `delete_memory` | Delete a specific memory by UUID. |
272
+ | `memory_history` | View change history for a memory (additions, updates, deletions). |
273
+
274
+ </details>
275
+
276
+ ---
277
+
278
+ ## 🌐 Supported Languages
279
+
280
+ **AST-aware** (functions and classes chunked intact):
281
+
282
+ <p>
283
+ <img src="https://img.shields.io/badge/JavaScript-F7DF1E?style=flat-square&logo=javascript&logoColor=black" alt="JavaScript"/>
284
+ <img src="https://img.shields.io/badge/TypeScript-3178C6?style=flat-square&logo=typescript&logoColor=white" alt="TypeScript"/>
285
+ <img src="https://img.shields.io/badge/React_(JSX/TSX)-61DAFB?style=flat-square&logo=react&logoColor=black" alt="JSX/TSX"/>
286
+ <img src="https://img.shields.io/badge/Python-3776AB?style=flat-square&logo=python&logoColor=white" alt="Python"/>
287
+ <img src="https://img.shields.io/badge/Go-00ADD8?style=flat-square&logo=go&logoColor=white" alt="Go"/>
288
+ <img src="https://img.shields.io/badge/Java-ED8B00?style=flat-square&logo=openjdk&logoColor=white" alt="Java"/>
289
+ <img src="https://img.shields.io/badge/Rust-000000?style=flat-square&logo=rust&logoColor=white" alt="Rust"/>
290
+ <img src="https://img.shields.io/badge/C-A8B9CC?style=flat-square&logo=c&logoColor=black" alt="C"/>
291
+ <img src="https://img.shields.io/badge/C++-00599C?style=flat-square&logo=cplusplus&logoColor=white" alt="C++"/>
292
+ <img src="https://img.shields.io/badge/C%23-512BD4?style=flat-square&logo=csharp&logoColor=white" alt="C#"/>
293
+ </p>
294
+
295
+ **Line-based fallback** (sliding window chunking for everything else):
296
+
297
+ <p>
298
+ <img src="https://img.shields.io/badge/Markdown-000000?style=flat-square&logo=markdown&logoColor=white" alt="Markdown"/>
299
+ <img src="https://img.shields.io/badge/YAML-CB171E?style=flat-square&logo=yaml&logoColor=white" alt="YAML"/>
300
+ <img src="https://img.shields.io/badge/JSON-000000?style=flat-square&logo=json&logoColor=white" alt="JSON"/>
301
+ <img src="https://img.shields.io/badge/Ruby-CC342D?style=flat-square&logo=ruby&logoColor=white" alt="Ruby"/>
302
+ <img src="https://img.shields.io/badge/PHP-777BB4?style=flat-square&logo=php&logoColor=white" alt="PHP"/>
303
+ <img src="https://img.shields.io/badge/Swift-F05138?style=flat-square&logo=swift&logoColor=white" alt="Swift"/>
304
+ <img src="https://img.shields.io/badge/Kotlin-7F52FF?style=flat-square&logo=kotlin&logoColor=white" alt="Kotlin"/>
305
+ <img src="https://img.shields.io/badge/and_more...-30363d?style=flat-square" alt="and more"/>
306
+ </p>
307
+
308
+ ---
309
+
310
+ ## 🛠️ Development
311
+
312
+ ```bash
313
+ npm install && npx tsc # install and build
314
+ npm run dev # watch mode (tsx)
315
+ npm test # unit tests (no external services needed)
316
+ npm run test:integration # requires Qdrant at localhost:6333 + OPENAI_API_KEY
317
+ ```
318
+
319
+ **Commit format:** `type(scope): description`
320
+ Types: `feat`, `fix`, `docs`, `refactor`, `perf`, `chore`, `test`
321
+ Scopes: `embedding`, `vectordb`, `splitter`, `indexer`, `mcp`, `infra`, `config`
322
+
323
+ ---
324
+
325
+ ## 🙏 Acknowledgements
326
+
327
+ Heavily inspired by [mem0](https://github.com/mem0ai/mem0), [claude-mem](https://github.com/thedotmack/claude-mem), and [claude-context](https://github.com/zilliztech/claude-context). Documentation retrieval powered by [context7](https://github.com/upstash/context7).
328
+
329
+ ---
330
+
331
+ ## 📄 License
332
+
333
+ MIT
package/dist/config.d.ts CHANGED
@@ -15,6 +15,11 @@ declare const configSchema: z.ZodEffects<z.ZodObject<{
15
15
  eideticDataDir: z.ZodDefault<z.ZodString>;
16
16
  customExtensions: z.ZodEffects<z.ZodDefault<z.ZodArray<z.ZodString, "many">>, string[], unknown>;
17
17
  customIgnorePatterns: z.ZodEffects<z.ZodDefault<z.ZodArray<z.ZodString, "many">>, string[], unknown>;
18
+ memoryLlmProvider: z.ZodDefault<z.ZodEnum<["openai", "ollama", "anthropic"]>>;
19
+ memoryLlmModel: z.ZodOptional<z.ZodString>;
20
+ memoryLlmBaseUrl: z.ZodOptional<z.ZodString>;
21
+ memoryLlmApiKey: z.ZodOptional<z.ZodString>;
22
+ anthropicApiKey: z.ZodDefault<z.ZodString>;
18
23
  }, "strip", z.ZodTypeAny, {
19
24
  embeddingProvider: "openai" | "ollama" | "local";
20
25
  openaiApiKey: string;
@@ -27,10 +32,15 @@ declare const configSchema: z.ZodEffects<z.ZodObject<{
27
32
  eideticDataDir: string;
28
33
  customExtensions: string[];
29
34
  customIgnorePatterns: string[];
35
+ memoryLlmProvider: "openai" | "ollama" | "anthropic";
36
+ anthropicApiKey: string;
30
37
  openaiBaseUrl?: string | undefined;
31
38
  embeddingModel?: string | undefined;
32
39
  qdrantApiKey?: string | undefined;
33
40
  milvusToken?: string | undefined;
41
+ memoryLlmModel?: string | undefined;
42
+ memoryLlmBaseUrl?: string | undefined;
43
+ memoryLlmApiKey?: string | undefined;
34
44
  }, {
35
45
  embeddingProvider?: "openai" | "ollama" | "local" | undefined;
36
46
  openaiApiKey?: string | undefined;
@@ -47,8 +57,14 @@ declare const configSchema: z.ZodEffects<z.ZodObject<{
47
57
  eideticDataDir?: string | undefined;
48
58
  customExtensions?: unknown;
49
59
  customIgnorePatterns?: unknown;
60
+ memoryLlmProvider?: "openai" | "ollama" | "anthropic" | undefined;
61
+ memoryLlmModel?: string | undefined;
62
+ memoryLlmBaseUrl?: string | undefined;
63
+ memoryLlmApiKey?: string | undefined;
64
+ anthropicApiKey?: string | undefined;
50
65
  }>, {
51
66
  embeddingModel: string;
67
+ memoryLlmModel: string;
52
68
  embeddingProvider: "openai" | "ollama" | "local";
53
69
  openaiApiKey: string;
54
70
  ollamaBaseUrl: string;
@@ -60,9 +76,13 @@ declare const configSchema: z.ZodEffects<z.ZodObject<{
60
76
  eideticDataDir: string;
61
77
  customExtensions: string[];
62
78
  customIgnorePatterns: string[];
79
+ memoryLlmProvider: "openai" | "ollama" | "anthropic";
80
+ anthropicApiKey: string;
63
81
  openaiBaseUrl?: string | undefined;
64
82
  qdrantApiKey?: string | undefined;
65
83
  milvusToken?: string | undefined;
84
+ memoryLlmBaseUrl?: string | undefined;
85
+ memoryLlmApiKey?: string | undefined;
66
86
  }, {
67
87
  embeddingProvider?: "openai" | "ollama" | "local" | undefined;
68
88
  openaiApiKey?: string | undefined;
@@ -79,6 +99,11 @@ declare const configSchema: z.ZodEffects<z.ZodObject<{
79
99
  eideticDataDir?: string | undefined;
80
100
  customExtensions?: unknown;
81
101
  customIgnorePatterns?: unknown;
102
+ memoryLlmProvider?: "openai" | "ollama" | "anthropic" | undefined;
103
+ memoryLlmModel?: string | undefined;
104
+ memoryLlmBaseUrl?: string | undefined;
105
+ memoryLlmApiKey?: string | undefined;
106
+ anthropicApiKey?: string | undefined;
82
107
  }>;
83
108
  export type Config = z.infer<typeof configSchema>;
84
109
  export declare function loadConfig(): Config;
package/dist/config.js CHANGED
@@ -2,7 +2,8 @@ import os from 'node:os';
2
2
  import path from 'node:path';
3
3
  import { z } from 'zod';
4
4
  import { ConfigError } from './errors.js';
5
- const configSchema = z.object({
5
+ const configSchema = z
6
+ .object({
6
7
  embeddingProvider: z.enum(['openai', 'ollama', 'local']).default('openai'),
7
8
  openaiApiKey: z.string().default(''),
8
9
  openaiBaseUrl: z.string().optional(),
@@ -16,43 +17,57 @@ const configSchema = z.object({
16
17
  milvusAddress: z.string().default('localhost:19530'),
17
18
  milvusToken: z.string().optional(),
18
19
  eideticDataDir: z.string().default(path.join(os.homedir(), '.eidetic')),
19
- customExtensions: z.preprocess((val) => typeof val === 'string' ? JSON.parse(val) : val, z.array(z.string()).default([])),
20
- customIgnorePatterns: z.preprocess((val) => typeof val === 'string' ? JSON.parse(val) : val, z.array(z.string()).default([])),
21
- }).transform((cfg) => ({
20
+ customExtensions: z.preprocess((val) => (typeof val === 'string' ? JSON.parse(val) : val), z.array(z.string()).default([])),
21
+ customIgnorePatterns: z.preprocess((val) => (typeof val === 'string' ? JSON.parse(val) : val), z.array(z.string()).default([])),
22
+ memoryLlmProvider: z.enum(['openai', 'ollama', 'anthropic']).default('anthropic'),
23
+ memoryLlmModel: z.string().optional(),
24
+ memoryLlmBaseUrl: z.string().optional(),
25
+ memoryLlmApiKey: z.string().optional(),
26
+ anthropicApiKey: z.string().default(''),
27
+ })
28
+ .transform((cfg) => ({
22
29
  ...cfg,
23
30
  // Default embedding model depends on provider
24
- embeddingModel: cfg.embeddingModel
25
- ?? (cfg.embeddingProvider === 'ollama' ? 'nomic-embed-text' : 'text-embedding-3-small'),
31
+ embeddingModel: cfg.embeddingModel ??
32
+ (cfg.embeddingProvider === 'ollama' ? 'nomic-embed-text' : 'text-embedding-3-small'),
33
+ // Default memory LLM model depends on provider
34
+ memoryLlmModel: cfg.memoryLlmModel ??
35
+ (cfg.memoryLlmProvider === 'ollama'
36
+ ? 'llama3.2'
37
+ : cfg.memoryLlmProvider === 'anthropic'
38
+ ? 'claude-haiku-4-5-20251001'
39
+ : 'gpt-4o-mini'),
26
40
  }));
27
41
  let cachedConfig = null;
28
42
  export function loadConfig() {
29
43
  const raw = {
30
44
  embeddingProvider: process.env.EMBEDDING_PROVIDER,
31
- openaiApiKey: process.env.OPENAI_API_KEY ?? '',
45
+ openaiApiKey: (process.env.OPENAI_API_KEY ?? '').trim().replace(/^["']|["']$/g, ''),
32
46
  openaiBaseUrl: process.env.OPENAI_BASE_URL || undefined,
33
47
  ollamaBaseUrl: process.env.OLLAMA_BASE_URL,
34
48
  embeddingModel: process.env.EMBEDDING_MODEL || undefined,
35
49
  embeddingBatchSize: process.env.EMBEDDING_BATCH_SIZE,
36
50
  indexingConcurrency: process.env.INDEXING_CONCURRENCY,
37
51
  qdrantUrl: process.env.QDRANT_URL,
38
- qdrantApiKey: process.env.QDRANT_API_KEY || undefined,
52
+ qdrantApiKey: process.env.QDRANT_API_KEY?.trim().replace(/^["']|["']$/g, '') || undefined,
39
53
  vectordbProvider: process.env.VECTORDB_PROVIDER,
40
54
  milvusAddress: process.env.MILVUS_ADDRESS,
41
- milvusToken: process.env.MILVUS_TOKEN || undefined,
55
+ milvusToken: process.env.MILVUS_TOKEN?.trim().replace(/^["']|["']$/g, '') || undefined,
42
56
  eideticDataDir: process.env.EIDETIC_DATA_DIR,
43
57
  customExtensions: process.env.CUSTOM_EXTENSIONS,
44
58
  customIgnorePatterns: process.env.CUSTOM_IGNORE_PATTERNS,
59
+ memoryLlmProvider: process.env.MEMORY_LLM_PROVIDER,
60
+ memoryLlmModel: process.env.MEMORY_LLM_MODEL || undefined,
61
+ memoryLlmBaseUrl: process.env.MEMORY_LLM_BASE_URL || undefined,
62
+ memoryLlmApiKey: process.env.MEMORY_LLM_API_KEY?.trim().replace(/^["']|["']$/g, '') || undefined,
63
+ anthropicApiKey: (process.env.ANTHROPIC_API_KEY ?? '').trim().replace(/^["']|["']$/g, ''),
45
64
  };
46
65
  const result = configSchema.safeParse(raw);
47
66
  if (!result.success) {
48
- const issues = result.error.issues.map(i => ` ${i.path.join('.')}: ${i.message}`).join('\n');
67
+ const issues = result.error.issues.map((i) => ` ${i.path.join('.')}: ${i.message}`).join('\n');
49
68
  throw new ConfigError(`Invalid configuration:\n${issues}`);
50
69
  }
51
70
  const config = result.data;
52
- if (config.embeddingProvider === 'openai' && !config.openaiApiKey) {
53
- throw new ConfigError('OPENAI_API_KEY is required when EMBEDDING_PROVIDER is "openai" (the default). ' +
54
- 'Set EMBEDDING_PROVIDER=ollama or EMBEDDING_PROVIDER=local to use a local embedding server.');
55
- }
56
71
  cachedConfig = config;
57
72
  return cachedConfig;
58
73
  }
@@ -0,0 +1,8 @@
1
+ import type { VectorDB } from '../vectordb/types.js';
2
+ export interface CleanupResult {
3
+ removedFiles: string[];
4
+ totalRemoved: number;
5
+ durationMs: number;
6
+ }
7
+ export declare function cleanupVectors(rootPath: string, vectordb: VectorDB, onProgress?: (pct: number, msg: string) => void, customExtensions?: string[], customIgnorePatterns?: string[]): Promise<CleanupResult>;
8
+ //# sourceMappingURL=cleanup.d.ts.map
@@ -0,0 +1,41 @@
1
+ import { scanFiles, buildSnapshot, diffSnapshots } from './sync.js';
2
+ import { loadSnapshot, saveSnapshot } from './snapshot-io.js';
3
+ import { pathToCollectionName } from '../paths.js';
4
+ import { IndexingError } from '../errors.js';
5
+ export async function cleanupVectors(rootPath, vectordb, onProgress, customExtensions, customIgnorePatterns) {
6
+ const startTime = Date.now();
7
+ const previousSnapshot = loadSnapshot(rootPath);
8
+ if (!previousSnapshot) {
9
+ throw new IndexingError(`No snapshot found for ${rootPath}. Index the codebase first before running cleanup.`);
10
+ }
11
+ onProgress?.(10, 'Scanning files on disk...');
12
+ const filePaths = await scanFiles(rootPath, customExtensions ?? [], customIgnorePatterns ?? []);
13
+ onProgress?.(40, 'Building current snapshot...');
14
+ const currentSnapshot = buildSnapshot(rootPath, filePaths);
15
+ onProgress?.(60, 'Diffing snapshots...');
16
+ const { removed } = diffSnapshots(previousSnapshot, currentSnapshot);
17
+ if (removed.length === 0) {
18
+ onProgress?.(100, 'No removed files found.');
19
+ return { removedFiles: [], totalRemoved: 0, durationMs: Date.now() - startTime };
20
+ }
21
+ const collectionName = pathToCollectionName(rootPath);
22
+ let deletedCount = 0;
23
+ for (const rel of removed) {
24
+ onProgress?.(60 + Math.round((deletedCount / removed.length) * 35), `Deleting vectors for ${rel}...`);
25
+ await vectordb.deleteByPath(collectionName, rel);
26
+ deletedCount++;
27
+ }
28
+ // Save updated snapshot (removes the deleted file entries)
29
+ const updatedSnapshot = { ...previousSnapshot };
30
+ for (const rel of removed) {
31
+ Reflect.deleteProperty(updatedSnapshot, rel);
32
+ }
33
+ saveSnapshot(rootPath, updatedSnapshot);
34
+ onProgress?.(100, 'Cleanup complete.');
35
+ return {
36
+ removedFiles: removed,
37
+ totalRemoved: removed.length,
38
+ durationMs: Date.now() - startTime,
39
+ };
40
+ }
41
+ //# sourceMappingURL=cleanup.js.map
@@ -0,0 +1,13 @@
1
+ import type { Embedding } from '../embedding/types.js';
2
+ import type { VectorDB } from '../vectordb/types.js';
3
+ export interface DocIndexResult {
4
+ library: string;
5
+ topic: string;
6
+ source: string;
7
+ collectionName: string;
8
+ totalChunks: number;
9
+ estimatedTokens: number;
10
+ durationMs: number;
11
+ }
12
+ export declare function indexDocument(content: string, source: string, library: string, topic: string, embedding: Embedding, vectordb: VectorDB, ttlDays?: number): Promise<DocIndexResult>;
13
+ //# sourceMappingURL=doc-indexer.d.ts.map
@@ -0,0 +1,76 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { LineSplitter } from '../splitter/line.js';
3
+ import { docCollectionName } from '../paths.js';
4
+ import { upsertDocEntry } from '../state/doc-metadata.js';
5
+ import { IndexingError } from '../errors.js';
6
+ import { getConfig } from '../config.js';
7
+ export async function indexDocument(content, source, library, topic, embedding, vectordb, ttlDays = 7) {
8
+ const start = Date.now();
9
+ if (!content || content.trim().length === 0) {
10
+ throw new IndexingError('Document content is empty.');
11
+ }
12
+ if (!source)
13
+ throw new IndexingError('Document source is required.');
14
+ if (!library)
15
+ throw new IndexingError('Library name is required.');
16
+ if (!topic)
17
+ throw new IndexingError('Topic is required.');
18
+ const collection = docCollectionName(library);
19
+ const config = getConfig();
20
+ const splitter = new LineSplitter();
21
+ const chunks = splitter.split(content, 'markdown', source);
22
+ if (chunks.length === 0) {
23
+ throw new IndexingError('Document produced no chunks after splitting.');
24
+ }
25
+ const exists = await vectordb.hasCollection(collection);
26
+ if (!exists) {
27
+ await vectordb.createCollection(collection, embedding.dimension);
28
+ }
29
+ try {
30
+ await vectordb.deleteByPath(collection, source);
31
+ }
32
+ catch {
33
+ // collection may be new with no matching docs
34
+ }
35
+ const batchSize = config.embeddingBatchSize;
36
+ let totalChunks = 0;
37
+ let totalTokens = 0;
38
+ for (let i = 0; i < chunks.length; i += batchSize) {
39
+ const batch = chunks.slice(i, i + batchSize);
40
+ const texts = batch.map((c) => c.content);
41
+ const estimation = embedding.estimateTokens(texts);
42
+ totalTokens += estimation.estimatedTokens;
43
+ const vectors = await embedding.embedBatch(texts);
44
+ const documents = batch.map((chunk, j) => ({
45
+ id: randomUUID(),
46
+ content: chunk.content,
47
+ vector: vectors[j],
48
+ relativePath: source,
49
+ startLine: chunk.startLine,
50
+ endLine: chunk.endLine,
51
+ fileExtension: '.md',
52
+ language: 'markdown',
53
+ }));
54
+ await vectordb.insert(collection, documents);
55
+ totalChunks += batch.length;
56
+ }
57
+ upsertDocEntry({
58
+ library,
59
+ topic,
60
+ source,
61
+ collectionName: collection,
62
+ indexedAt: new Date().toISOString(),
63
+ ttlDays,
64
+ totalChunks,
65
+ });
66
+ return {
67
+ library,
68
+ topic,
69
+ source,
70
+ collectionName: collection,
71
+ totalChunks,
72
+ estimatedTokens: totalTokens,
73
+ durationMs: Date.now() - start,
74
+ };
75
+ }
76
+ //# sourceMappingURL=doc-indexer.js.map
@@ -0,0 +1,13 @@
1
+ import type { Embedding } from '../embedding/types.js';
2
+ import type { VectorDB, SearchResult } from '../vectordb/types.js';
3
+ export interface DocSearchResult extends SearchResult {
4
+ library: string;
5
+ topic: string;
6
+ source: string;
7
+ stale: boolean;
8
+ }
9
+ export declare function searchDocuments(query: string, embedding: Embedding, vectordb: VectorDB, options?: {
10
+ library?: string;
11
+ limit?: number;
12
+ }): Promise<DocSearchResult[]>;
13
+ //# sourceMappingURL=doc-searcher.d.ts.map