directory-indexer 0.3.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -14,7 +14,7 @@ Self-hosted semantic search for local files. Give your AI assistant access to se
14
14
  **Prerequisites:**
15
15
 
16
16
  - **[Docker](https://docs.docker.com/get-docker/)** - For running Qdrant and Ollama _(skip if you already have them running natively)_
17
- - **[Node.js 18+](https://nodejs.org/en/download/)** - Required for running directory-indexer
17
+ - **[Node.js 18+](https://nodejs.org/en/download/)** - Required for running directory-indexer (the v1.x line targets Node 18 LTS compatibility)
18
18
 
19
19
  _Note: For native Qdrant and Ollama installation without Docker, see [Setup section](#setup)._
20
20
 
@@ -274,6 +274,9 @@ export DIRECTORY_INDEXER_DATA_DIR="/opt/ai-knowledge-base"
274
274
  export QDRANT_ENDPOINT="http://localhost:6333"
275
275
  export OLLAMA_ENDPOINT="http://localhost:11434"
276
276
 
277
+ # OpenAI provider: custom OpenAI-compatible endpoint (default: https://api.openai.com/v1)
278
+ export OPENAI_ENDPOINT="https://api.openai.com/v1"
279
+
277
280
  # Optional API keys
278
281
  export OPENAI_API_KEY="your-key-here"
279
282
  export QDRANT_API_KEY="your-key-here"
@@ -9,13 +9,16 @@ async function handleIndex(paths, options) {
9
9
  const config = await loadConfig({ verbose: options.verbose });
10
10
  await validateIndexPrerequisites(config);
11
11
  console.log(`Indexing ${paths.length} ${paths.length === 1 ? "directory" : "directories"}: ${paths.join(", ")}`);
12
- if (!options.verbose) {
12
+ if (!config.verbose) {
13
13
  console.log("Run with --verbose for detailed per-file indexing reports");
14
14
  console.log("Indexing can be safely stopped and resumed - progress is automatically saved");
15
15
  console.log("You can start using the MCP server while indexing continues");
16
16
  console.log("Indexing may take time due to embedding generation - see project README for performance tips");
17
17
  }
18
- const result = await indexDirectories(paths, config);
18
+ const result = await indexDirectories(paths, config, {
19
+ progress: (m) => console.log(m),
20
+ error: (m) => console.error(m)
21
+ });
19
22
  console.log(`Indexed ${result.indexed} files, skipped ${result.skipped} files, cleaned up ${result.deleted} deleted files, ${result.failed} failed`);
20
23
  if (result.errors.length > 0) {
21
24
  console.log(`Errors: [`);
@@ -1 +1 @@
1
- {"version":3,"file":"cli-handlers.js","sources":["../src/cli-handlers.ts"],"sourcesContent":["import { indexDirectories } from './indexing.js';\nimport { searchContent, findSimilarFiles, getFileContent } from './search.js';\nimport { loadConfig } from './config.js';\nimport { getIndexStatus } from './storage.js';\nimport { startMcpServer } from './mcp.js';\nimport { validateIndexPrerequisites, validateSearchPrerequisites, getServiceStatus } from './prerequisites.js';\nimport { resetEnvironment } from './reset.js';\n\nexport interface IndexOptions {\n verbose?: boolean;\n}\n\nexport interface SearchOptions {\n limit?: number;\n showChunks?: boolean;\n verbose?: boolean;\n}\n\nexport interface SimilarOptions {\n limit?: number;\n verbose?: boolean;\n}\n\nexport interface GetOptions {\n chunks?: string;\n verbose?: boolean;\n}\n\nexport interface ServeOptions {\n verbose?: boolean;\n}\n\nexport interface ResetOptions {\n force?: boolean;\n verbose?: boolean;\n}\n\nexport interface StatusOptions {\n verbose?: boolean;\n}\n\nexport async function handleIndex(paths: string[], options: IndexOptions): Promise<void> {\n const config = await loadConfig({ verbose: options.verbose });\n await validateIndexPrerequisites(config);\n \n console.log(`Indexing ${paths.length} ${paths.length === 1 ? 'directory' : 'directories'}: ${paths.join(', ')}`);\n if (!options.verbose) {\n console.log('Run with --verbose for detailed per-file indexing reports');\n console.log('Indexing can be safely stopped and resumed - progress is automatically saved');\n console.log('You can start using the MCP server while indexing continues');\n console.log('Indexing may take time due to embedding generation - see project README for performance tips');\n }\n \n const result = await indexDirectories(paths, config);\n console.log(`Indexed ${result.indexed} files, skipped ${result.skipped} files, cleaned up ${result.deleted} deleted files, ${result.failed} failed`);\n \n if (result.errors.length > 0) {\n console.log(`Errors: [`);\n result.errors.forEach(error => {\n console.log(` '${error}'`);\n });\n console.log(`]`);\n }\n}\n\nexport async function handleSearch(query: string, options: SearchOptions): Promise<void> {\n const config = await loadConfig({ verbose: options.verbose });\n await validateSearchPrerequisites(config);\n \n const results = await searchContent(query, { limit: options.limit || 10 });\n \n if (results.length === 0) {\n console.log('No results found');\n return;\n }\n\n console.log(`Found ${results.length} results:\\n`);\n results.forEach((result, index) => {\n console.log(`${index + 1}. ${result.filePath}`);\n console.log(` Score: ${result.score.toFixed(3)} (${result.matchingChunks} chunks)`);\n \n if (options.showChunks && result.chunks.length > 0) {\n console.log(` Chunks:`);\n result.chunks.forEach(chunk => {\n console.log(` - Chunk ${chunk.chunkId}: ${chunk.score.toFixed(3)}`);\n });\n }\n \n console.log();\n });\n}\n\nexport async function handleSimilar(filePath: string, options: SimilarOptions): Promise<void> {\n const config = await loadConfig({ verbose: options.verbose });\n await validateSearchPrerequisites(config);\n \n const results = await findSimilarFiles(filePath, options.limit || 10);\n \n if (results.length === 0) {\n console.log('No similar files found');\n return;\n }\n\n console.log(`Found ${results.length} similar files:\\n`);\n results.forEach((result, index) => {\n console.log(`${index + 1}. ${result.filePath}`);\n console.log(` Similarity: ${result.score.toFixed(3)}`);\n console.log();\n });\n}\n\nexport async function handleGet(filePath: string, options: GetOptions): Promise<void> {\n await loadConfig({ verbose: options.verbose });\n const content = await getFileContent(filePath, options.chunks);\n console.log(content);\n}\n\nexport async function handleServe(options: ServeOptions): Promise<void> {\n const config = await loadConfig({ verbose: options.verbose });\n await startMcpServer(config);\n}\n\nexport async function handleReset(options: ResetOptions): Promise<void> {\n const config = await loadConfig({ verbose: options.verbose });\n await resetEnvironment(config, { \n force: options.force, \n verbose: options.verbose \n });\n}\n\nexport async function handleStatus(options: StatusOptions): Promise<void> {\n const config = await loadConfig({ verbose: options.verbose });\n const [status, serviceStatus] = await Promise.all([\n getIndexStatus(),\n getServiceStatus(config)\n ]);\n \n console.log('Directory Indexer Status Report');\n console.log('=====================================');\n console.log('');\n console.log('SERVICE STATUS:');\n console.log(` • Qdrant database: ${serviceStatus.qdrant ? 'Connected' : 'Disconnected'}`);\n console.log(` • Embedding service (${serviceStatus.embeddingProvider}): ${serviceStatus.embedding ? 'Connected' : 'Disconnected'}`);\n console.log('');\n console.log('OVERVIEW:');\n console.log(` • ${status.directoriesIndexed} directories have been indexed`);\n console.log(` • ${status.filesIndexed} files processed for semantic search`);\n console.log(` • ${status.chunksIndexed} text chunks available for AI search`);\n console.log(` • Database storage: ${status.databaseSize}`);\n console.log(` • Most recent indexing: ${status.lastIndexed || 'No indexing performed yet'}`);\n \n if (status.errors.length > 0) {\n console.log(` • Processing errors encountered: ${status.errors.length}`);\n if (options.verbose) {\n console.log('');\n console.log('RECENT ERRORS:');\n status.errors.slice(0, 5).forEach(error => {\n console.log(` - ${error}`);\n });\n }\n }\n \n console.log('');\n console.log('INDEXED DIRECTORIES:');\n if (status.directories.length === 0) {\n console.log(' No directories have been indexed yet.');\n console.log(' Run \"directory-indexer index <path>\" to start indexing.');\n } else {\n status.directories.forEach(dir => {\n console.log('');\n console.log(` Directory: ${dir.path}`);\n console.log(` • Indexing status: ${dir.status}`);\n console.log(` • Files processed: ${dir.filesCount}`);\n console.log(` • Searchable chunks: ${dir.chunksCount}`);\n console.log(` • Last indexed: ${dir.lastIndexed || 'Never completed'}`);\n if (dir.errors.length > 0) {\n console.log(` • Files with errors: ${dir.errors.length}`);\n if (options.verbose) {\n console.log(' • Recent errors:');\n dir.errors.slice(0, 3).forEach(error => {\n console.log(` - ${error}`);\n });\n }\n }\n });\n }\n \n if (status.workspaces.length > 0) {\n console.log('');\n console.log('WORKSPACES:');\n console.log(` • ${status.workspaceHealth.healthy} healthy, ${status.workspaceHealth.warnings} warnings, ${status.workspaceHealth.errors} errors`);\n \n if (status.workspaceHealth.errors > 0) {\n console.log('');\n console.log('WORKSPACE ERRORS:');\n status.workspaceHealth.criticalIssues.forEach(issue => {\n console.log(` ❌ ${issue}`);\n });\n }\n \n if (status.workspaceHealth.recommendations.length > 0) {\n console.log('');\n console.log('WORKSPACE RECOMMENDATIONS:');\n status.workspaceHealth.recommendations.forEach(rec => {\n console.log(` 💡 ${rec}`);\n });\n }\n \n if (options.verbose) {\n console.log('');\n console.log('WORKSPACE DETAILS:');\n status.workspaces.forEach(workspace => {\n console.log('');\n console.log(` Workspace: ${workspace.name}`);\n console.log(` • Status: ${workspace.health.status}`);\n console.log(` • Paths: ${workspace.paths.join(', ')}`);\n console.log(` • Files: ${workspace.filesCount}, Chunks: ${workspace.chunksCount}`);\n if (workspace.health.issues.length > 0) {\n console.log(` • Issues: ${workspace.health.issues.join('; ')}`);\n }\n });\n }\n }\n\n if (!status.qdrantConsistency.isConsistent) {\n console.log('');\n console.log('SYSTEM STATUS:');\n status.qdrantConsistency.issues.forEach(issue => {\n console.log(` • ${issue}`);\n });\n console.log('');\n console.log('Note: Status messages above may be normal during setup or active indexing.');\n } else {\n console.log('');\n console.log('SYSTEM STATUS:');\n console.log(' • All systems operational - ready for AI-powered search');\n }\n}"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cli-handlers.js","sources":["../src/cli-handlers.ts"],"sourcesContent":["import { indexDirectories } from './indexing.js';\nimport { searchContent, findSimilarFiles, getFileContent } from './search.js';\nimport { loadConfig } from './config.js';\nimport { getIndexStatus } from './storage.js';\nimport { startMcpServer } from './mcp.js';\nimport { validateIndexPrerequisites, validateSearchPrerequisites, getServiceStatus } from './prerequisites.js';\nimport { resetEnvironment } from './reset.js';\n\nexport interface IndexOptions {\n verbose?: boolean;\n}\n\nexport interface SearchOptions {\n limit?: number;\n showChunks?: boolean;\n verbose?: boolean;\n}\n\nexport interface SimilarOptions {\n limit?: number;\n verbose?: boolean;\n}\n\nexport interface GetOptions {\n chunks?: string;\n verbose?: boolean;\n}\n\nexport interface ServeOptions {\n verbose?: boolean;\n}\n\nexport interface ResetOptions {\n force?: boolean;\n verbose?: boolean;\n}\n\nexport interface StatusOptions {\n verbose?: boolean;\n}\n\nexport async function handleIndex(paths: string[], options: IndexOptions): Promise<void> {\n const config = await loadConfig({ verbose: options.verbose });\n await validateIndexPrerequisites(config);\n \n console.log(`Indexing ${paths.length} ${paths.length === 1 ? 'directory' : 'directories'}: ${paths.join(', ')}`);\n if (!config.verbose) {\n console.log('Run with --verbose for detailed per-file indexing reports');\n console.log('Indexing can be safely stopped and resumed - progress is automatically saved');\n console.log('You can start using the MCP server while indexing continues');\n console.log('Indexing may take time due to embedding generation - see project README for performance tips');\n }\n \n const result = await indexDirectories(paths, config, {\n progress: m => console.log(m),\n error: m => console.error(m)\n });\n console.log(`Indexed ${result.indexed} files, skipped ${result.skipped} files, cleaned up ${result.deleted} deleted files, ${result.failed} failed`);\n \n if (result.errors.length > 0) {\n console.log(`Errors: [`);\n result.errors.forEach(error => {\n console.log(` '${error}'`);\n });\n console.log(`]`);\n }\n}\n\nexport async function handleSearch(query: string, options: SearchOptions): Promise<void> {\n const config = await loadConfig({ verbose: options.verbose });\n await validateSearchPrerequisites(config);\n \n const results = await searchContent(query, { limit: options.limit || 10 });\n \n if (results.length === 0) {\n console.log('No results found');\n return;\n }\n\n console.log(`Found ${results.length} results:\\n`);\n results.forEach((result, index) => {\n console.log(`${index + 1}. ${result.filePath}`);\n console.log(` Score: ${result.score.toFixed(3)} (${result.matchingChunks} chunks)`);\n \n if (options.showChunks && result.chunks.length > 0) {\n console.log(` Chunks:`);\n result.chunks.forEach(chunk => {\n console.log(` - Chunk ${chunk.chunkId}: ${chunk.score.toFixed(3)}`);\n });\n }\n \n console.log();\n });\n}\n\nexport async function handleSimilar(filePath: string, options: SimilarOptions): Promise<void> {\n const config = await loadConfig({ verbose: options.verbose });\n await validateSearchPrerequisites(config);\n \n const results = await findSimilarFiles(filePath, options.limit || 10);\n \n if (results.length === 0) {\n console.log('No similar files found');\n return;\n }\n\n console.log(`Found ${results.length} similar files:\\n`);\n results.forEach((result, index) => {\n console.log(`${index + 1}. ${result.filePath}`);\n console.log(` Similarity: ${result.score.toFixed(3)}`);\n console.log();\n });\n}\n\nexport async function handleGet(filePath: string, options: GetOptions): Promise<void> {\n await loadConfig({ verbose: options.verbose });\n const content = await getFileContent(filePath, options.chunks);\n console.log(content);\n}\n\nexport async function handleServe(options: ServeOptions): Promise<void> {\n const config = await loadConfig({ verbose: options.verbose });\n await startMcpServer(config);\n}\n\nexport async function handleReset(options: ResetOptions): Promise<void> {\n const config = await loadConfig({ verbose: options.verbose });\n await resetEnvironment(config, { \n force: options.force, \n verbose: options.verbose \n });\n}\n\nexport async function handleStatus(options: StatusOptions): Promise<void> {\n const config = await loadConfig({ verbose: options.verbose });\n const [status, serviceStatus] = await Promise.all([\n getIndexStatus(),\n getServiceStatus(config)\n ]);\n \n console.log('Directory Indexer Status Report');\n console.log('=====================================');\n console.log('');\n console.log('SERVICE STATUS:');\n console.log(` • Qdrant database: ${serviceStatus.qdrant ? 'Connected' : 'Disconnected'}`);\n console.log(` • Embedding service (${serviceStatus.embeddingProvider}): ${serviceStatus.embedding ? 'Connected' : 'Disconnected'}`);\n console.log('');\n console.log('OVERVIEW:');\n console.log(` • ${status.directoriesIndexed} directories have been indexed`);\n console.log(` • ${status.filesIndexed} files processed for semantic search`);\n console.log(` • ${status.chunksIndexed} text chunks available for AI search`);\n console.log(` • Database storage: ${status.databaseSize}`);\n console.log(` • Most recent indexing: ${status.lastIndexed || 'No indexing performed yet'}`);\n \n if (status.errors.length > 0) {\n console.log(` • Processing errors encountered: ${status.errors.length}`);\n if (options.verbose) {\n console.log('');\n console.log('RECENT ERRORS:');\n status.errors.slice(0, 5).forEach(error => {\n console.log(` - ${error}`);\n });\n }\n }\n \n console.log('');\n console.log('INDEXED DIRECTORIES:');\n if (status.directories.length === 0) {\n console.log(' No directories have been indexed yet.');\n console.log(' Run \"directory-indexer index <path>\" to start indexing.');\n } else {\n status.directories.forEach(dir => {\n console.log('');\n console.log(` Directory: ${dir.path}`);\n console.log(` • Indexing status: ${dir.status}`);\n console.log(` • Files processed: ${dir.filesCount}`);\n console.log(` • Searchable chunks: ${dir.chunksCount}`);\n console.log(` • Last indexed: ${dir.lastIndexed || 'Never completed'}`);\n if (dir.errors.length > 0) {\n console.log(` • Files with errors: ${dir.errors.length}`);\n if (options.verbose) {\n console.log(' • Recent errors:');\n dir.errors.slice(0, 3).forEach(error => {\n console.log(` - ${error}`);\n });\n }\n }\n });\n }\n \n if (status.workspaces.length > 0) {\n console.log('');\n console.log('WORKSPACES:');\n console.log(` • ${status.workspaceHealth.healthy} healthy, ${status.workspaceHealth.warnings} warnings, ${status.workspaceHealth.errors} errors`);\n \n if (status.workspaceHealth.errors > 0) {\n console.log('');\n console.log('WORKSPACE ERRORS:');\n status.workspaceHealth.criticalIssues.forEach(issue => {\n console.log(` ❌ ${issue}`);\n });\n }\n \n if (status.workspaceHealth.recommendations.length > 0) {\n console.log('');\n console.log('WORKSPACE RECOMMENDATIONS:');\n status.workspaceHealth.recommendations.forEach(rec => {\n console.log(` 💡 ${rec}`);\n });\n }\n \n if (options.verbose) {\n console.log('');\n console.log('WORKSPACE DETAILS:');\n status.workspaces.forEach(workspace => {\n console.log('');\n console.log(` Workspace: ${workspace.name}`);\n console.log(` • Status: ${workspace.health.status}`);\n console.log(` • Paths: ${workspace.paths.join(', ')}`);\n console.log(` • Files: ${workspace.filesCount}, Chunks: ${workspace.chunksCount}`);\n if (workspace.health.issues.length > 0) {\n console.log(` • Issues: ${workspace.health.issues.join('; ')}`);\n }\n });\n }\n }\n\n if (!status.qdrantConsistency.isConsistent) {\n console.log('');\n console.log('SYSTEM STATUS:');\n status.qdrantConsistency.issues.forEach(issue => {\n console.log(` • ${issue}`);\n });\n console.log('');\n console.log('Note: Status messages above may be normal during setup or active indexing.');\n } else {\n console.log('');\n console.log('SYSTEM STATUS:');\n console.log(' • All systems operational - ready for AI-powered search');\n }\n}"],"names":[],"mappings":"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"}
package/dist/cli.js CHANGED
@@ -8,6 +8,13 @@ const __dirname$1 = dirname(fileURLToPath(import.meta.url));
8
8
  const packageJsonPath = join(__dirname$1, "../package.json");
9
9
  const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
10
10
  const VERSION = packageJson.version;
11
+ function parseLimit(value) {
12
+ const limit = Number.parseInt(value);
13
+ if (Number.isNaN(limit) || limit < 1) {
14
+ throw new Error("Invalid --limit: must be a positive integer");
15
+ }
16
+ return limit;
17
+ }
11
18
  async function main() {
12
19
  const program = new Command();
13
20
  program.name("directory-indexer").description("AI-powered directory indexing with semantic search").version(VERSION);
@@ -22,7 +29,7 @@ async function main() {
22
29
  program.command("search").description("Search indexed content semantically").argument("<query>", "Search query").option("-l, --limit <number>", "Maximum number of results", "10").option("-c, --show-chunks", "Show individual chunk scores and IDs").option("-v, --verbose", "Enable verbose logging").action(async (query, options) => {
23
30
  try {
24
31
  await handleSearch(query, {
25
- limit: parseInt(options.limit),
32
+ limit: parseLimit(options.limit),
26
33
  showChunks: options.showChunks,
27
34
  verbose: options.verbose
28
35
  });
@@ -34,7 +41,7 @@ async function main() {
34
41
  program.command("similar").description("Find files similar to a given file").argument("<file>", "File path to find similar files for").option("-l, --limit <number>", "Maximum number of results", "10").option("-v, --verbose", "Enable verbose logging").action(async (filePath, options) => {
35
42
  try {
36
43
  await handleSimilar(filePath, {
37
- limit: parseInt(options.limit),
44
+ limit: parseLimit(options.limit),
38
45
  verbose: options.verbose
39
46
  });
40
47
  } catch (error) {
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { Command } from 'commander';\nimport { fileURLToPath } from 'url';\nimport { readFileSync } from 'fs';\nimport { join, dirname } from 'path';\nimport { \n handleIndex, \n handleSearch, \n handleSimilar, \n handleGet, \n handleServe, \n handleReset, \n handleStatus \n} from './cli-handlers.js';\n\n// Read version from package.json\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst packageJsonPath = join(__dirname, '../package.json');\nconst packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));\nconst VERSION = packageJson.version;\n\nexport async function main() {\n const program = new Command();\n \n program\n .name('directory-indexer')\n .description('AI-powered directory indexing with semantic search')\n .version(VERSION);\n\n program\n .command('index')\n .description('Index directories for semantic search')\n .argument('<paths...>', 'Directory paths to index')\n .option('-v, --verbose', 'Enable verbose logging')\n .action(async (paths: string[], options) => {\n try {\n await handleIndex(paths, options);\n } catch (error) {\n console.error('Error indexing directories:', error);\n process.exit(1);\n }\n });\n\n program\n .command('search')\n .description('Search indexed content semantically')\n .argument('<query>', 'Search query')\n .option('-l, --limit <number>', 'Maximum number of results', '10')\n .option('-c, --show-chunks', 'Show individual chunk scores and IDs')\n .option('-v, --verbose', 'Enable verbose logging')\n .action(async (query: string, options) => {\n try {\n await handleSearch(query, {\n limit: parseInt(options.limit),\n showChunks: options.showChunks,\n verbose: options.verbose\n });\n } catch (error) {\n console.error('Error searching content:', error);\n process.exit(1);\n }\n });\n\n program\n .command('similar')\n .description('Find files similar to a given file')\n .argument('<file>', 'File path to find similar files for')\n .option('-l, --limit <number>', 'Maximum number of results', '10')\n .option('-v, --verbose', 'Enable verbose logging')\n .action(async (filePath: string, options) => {\n try {\n await handleSimilar(filePath, {\n limit: parseInt(options.limit),\n verbose: options.verbose\n });\n } catch (error) {\n console.error('Error finding similar files:', error);\n process.exit(1);\n }\n });\n\n program\n .command('get')\n .description('Get file content')\n .argument('<file>', 'File path to retrieve')\n .option('-c, --chunks <range>', 'Chunk range (e.g., \"2-5\")')\n .option('-v, --verbose', 'Enable verbose logging')\n .action(async (filePath: string, options) => {\n try {\n await handleGet(filePath, options);\n } catch (error) {\n console.error('Error getting file content:', error);\n process.exit(1);\n }\n });\n\n program\n .command('serve')\n .description('Start MCP server')\n .option('-v, --verbose', 'Enable verbose logging')\n .action(async (options) => {\n try {\n await handleServe(options);\n } catch (error) {\n console.error('Error starting MCP server:', error);\n process.exit(1);\n }\n });\n\n program\n .command('reset')\n .description('Reset directory-indexer data (database and vector collection)')\n .option('--force', 'Skip confirmation prompt')\n .option('-v, --verbose', 'Enable verbose logging')\n .action(async (options) => {\n try {\n await handleReset(options);\n } catch (error) {\n if (error instanceof Error && error.message === 'Reset cancelled by user') {\n console.log('\\nReset cancelled.');\n process.exit(0);\n }\n console.error('Error during reset:', error);\n process.exit(1);\n }\n });\n\n program\n .command('status')\n .description('Show indexing status')\n .option('-v, --verbose', 'Enable verbose logging')\n .action(async (options) => {\n try {\n await handleStatus(options);\n } catch (error) {\n console.error('Error getting status:', error);\n process.exit(1);\n }\n });\n\n await program.parseAsync();\n}\n\n// Main function is already exported above"],"names":["__dirname"],"mappings":"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"}
1
+ {"version":3,"file":"cli.js","sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { Command } from 'commander';\nimport { fileURLToPath } from 'url';\nimport { readFileSync } from 'fs';\nimport { join, dirname } from 'path';\nimport { \n handleIndex, \n handleSearch, \n handleSimilar, \n handleGet, \n handleServe, \n handleReset, \n handleStatus \n} from './cli-handlers.js';\n\n// Read version from package.json\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst packageJsonPath = join(__dirname, '../package.json');\nconst packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));\nconst VERSION = packageJson.version;\n\nfunction parseLimit(value: string): number {\n const limit = Number.parseInt(value);\n if (Number.isNaN(limit) || limit < 1) {\n throw new Error('Invalid --limit: must be a positive integer');\n }\n return limit;\n}\n\nexport async function main() {\n const program = new Command();\n\n program\n .name('directory-indexer')\n .description('AI-powered directory indexing with semantic search')\n .version(VERSION);\n\n program\n .command('index')\n .description('Index directories for semantic search')\n .argument('<paths...>', 'Directory paths to index')\n .option('-v, --verbose', 'Enable verbose logging')\n .action(async (paths: string[], options) => {\n try {\n await handleIndex(paths, options);\n } catch (error) {\n console.error('Error indexing directories:', error);\n process.exit(1);\n }\n });\n\n program\n .command('search')\n .description('Search indexed content semantically')\n .argument('<query>', 'Search query')\n .option('-l, --limit <number>', 'Maximum number of results', '10')\n .option('-c, --show-chunks', 'Show individual chunk scores and IDs')\n .option('-v, --verbose', 'Enable verbose logging')\n .action(async (query: string, options) => {\n try {\n await handleSearch(query, {\n limit: parseLimit(options.limit),\n showChunks: options.showChunks,\n verbose: options.verbose\n });\n } catch (error) {\n console.error('Error searching content:', error);\n process.exit(1);\n }\n });\n\n program\n .command('similar')\n .description('Find files similar to a given file')\n .argument('<file>', 'File path to find similar files for')\n .option('-l, --limit <number>', 'Maximum number of results', '10')\n .option('-v, --verbose', 'Enable verbose logging')\n .action(async (filePath: string, options) => {\n try {\n await handleSimilar(filePath, {\n limit: parseLimit(options.limit),\n verbose: options.verbose\n });\n } catch (error) {\n console.error('Error finding similar files:', error);\n process.exit(1);\n }\n });\n\n program\n .command('get')\n .description('Get file content')\n .argument('<file>', 'File path to retrieve')\n .option('-c, --chunks <range>', 'Chunk range (e.g., \"2-5\")')\n .option('-v, --verbose', 'Enable verbose logging')\n .action(async (filePath: string, options) => {\n try {\n await handleGet(filePath, options);\n } catch (error) {\n console.error('Error getting file content:', error);\n process.exit(1);\n }\n });\n\n program\n .command('serve')\n .description('Start MCP server')\n .option('-v, --verbose', 'Enable verbose logging')\n .action(async (options) => {\n try {\n await handleServe(options);\n } catch (error) {\n console.error('Error starting MCP server:', error);\n process.exit(1);\n }\n });\n\n program\n .command('reset')\n .description('Reset directory-indexer data (database and vector collection)')\n .option('--force', 'Skip confirmation prompt')\n .option('-v, --verbose', 'Enable verbose logging')\n .action(async (options) => {\n try {\n await handleReset(options);\n } catch (error) {\n if (error instanceof Error && error.message === 'Reset cancelled by user') {\n console.log('\\nReset cancelled.');\n process.exit(0);\n }\n console.error('Error during reset:', error);\n process.exit(1);\n }\n });\n\n program\n .command('status')\n .description('Show indexing status')\n .option('-v, --verbose', 'Enable verbose logging')\n .action(async (options) => {\n try {\n await handleStatus(options);\n } catch (error) {\n console.error('Error getting status:', error);\n process.exit(1);\n }\n });\n\n await program.parseAsync();\n}\n\n// Main function is already exported above"],"names":["__dirname"],"mappings":"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"}
package/dist/config.js CHANGED
@@ -12,14 +12,14 @@ const WorkspaceSchema = z.object({
12
12
  const ConfigSchema = z.object({
13
13
  storage: z.object({
14
14
  sqlitePath: z.string(),
15
- qdrantEndpoint: z.string().url(),
15
+ qdrantEndpoint: z.url(),
16
16
  qdrantCollection: z.string(),
17
17
  qdrantApiKey: z.string().optional()
18
18
  }),
19
19
  embedding: z.object({
20
20
  provider: z.enum(["ollama", "openai", "mock"]),
21
21
  model: z.string(),
22
- endpoint: z.string().url()
22
+ endpoint: z.url()
23
23
  }),
24
24
  indexing: z.object({
25
25
  chunkSize: z.number().positive(),
@@ -27,10 +27,18 @@ const ConfigSchema = z.object({
27
27
  maxFileSize: z.number().positive(),
28
28
  ignorePatterns: z.array(z.string()),
29
29
  respectGitignore: z.boolean()
30
+ }).superRefine((indexing, ctx) => {
31
+ if (indexing.chunkOverlap >= indexing.chunkSize) {
32
+ ctx.addIssue({
33
+ code: "custom",
34
+ path: ["chunkOverlap"],
35
+ message: "chunkOverlap must be smaller than chunkSize"
36
+ });
37
+ }
30
38
  }),
31
39
  dataDir: z.string(),
32
40
  verbose: z.boolean(),
33
- workspaces: z.record(WorkspaceSchema)
41
+ workspaces: z.record(z.string(), WorkspaceSchema)
34
42
  });
35
43
  class ConfigError extends Error {
36
44
  constructor(message, cause) {
@@ -92,7 +100,7 @@ function loadConfig(options = {}) {
92
100
  embedding: {
93
101
  provider: process.env.EMBEDDING_PROVIDER || "ollama",
94
102
  model: process.env.EMBEDDING_MODEL || "nomic-embed-text",
95
- endpoint: process.env.OLLAMA_ENDPOINT || "http://127.0.0.1:11434"
103
+ endpoint: process.env.EMBEDDING_PROVIDER === "openai" ? process.env.OPENAI_ENDPOINT || "https://api.openai.com/v1" : process.env.OLLAMA_ENDPOINT || "http://127.0.0.1:11434"
96
104
  },
97
105
  indexing: {
98
106
  chunkSize: parseInt(process.env.CHUNK_SIZE || "512"),
@@ -109,7 +117,7 @@ function loadConfig(options = {}) {
109
117
  return ConfigSchema.parse(config);
110
118
  } catch (error) {
111
119
  if (error instanceof z.ZodError) {
112
- const messages = error.errors.map((e) => `${e.path.join(".")}: ${e.message}`);
120
+ const messages = error.issues.map((e) => `${e.path.join(".")}: ${e.message}`);
113
121
  throw new ConfigError(`Configuration validation failed: ${messages.join(", ")}`, error);
114
122
  }
115
123
  throw new ConfigError("Failed to load configuration", error);
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sources":["../src/config.ts"],"sourcesContent":["import { homedir } from 'os';\nimport { join } from 'path';\nimport { existsSync, statSync } from 'fs';\nimport { z } from 'zod';\nimport { normalizePath } from './utils';\n\nconst WorkspaceSchema = z.object({\n paths: z.array(z.string()),\n isValid: z.boolean(),\n filesCount: z.number().optional(),\n chunksCount: z.number().optional(),\n});\n\nconst ConfigSchema = z.object({\n storage: z.object({\n sqlitePath: z.string(),\n qdrantEndpoint: z.string().url(),\n qdrantCollection: z.string(),\n qdrantApiKey: z.string().optional(),\n }),\n embedding: z.object({\n provider: z.enum(['ollama', 'openai', 'mock']),\n model: z.string(),\n endpoint: z.string().url(),\n }),\n indexing: z.object({\n chunkSize: z.number().positive(),\n chunkOverlap: z.number().nonnegative(),\n maxFileSize: z.number().positive(),\n ignorePatterns: z.array(z.string()),\n respectGitignore: z.boolean(),\n }),\n dataDir: z.string(),\n verbose: z.boolean(),\n workspaces: z.record(WorkspaceSchema),\n});\n\nexport type Config = z.infer<typeof ConfigSchema>;\nexport type WorkspaceConfig = z.infer<typeof WorkspaceSchema>;\n\nexport class ConfigError extends Error {\n constructor(message: string, public override cause?: Error) {\n super(message);\n this.name = 'ConfigError';\n }\n}\n\nfunction parseWorkspaces(env: Record<string, string | undefined>): Record<string, WorkspaceConfig> {\n const workspaces: Record<string, WorkspaceConfig> = {};\n \n for (const [key, value] of Object.entries(env)) {\n if (key.startsWith('WORKSPACE_') && value) {\n const name = key.replace('WORKSPACE_', '').toLowerCase();\n \n // Parse paths from comma-separated string or JSON array\n let paths: string[];\n try {\n // Try parsing as JSON array first\n paths = JSON.parse(value);\n if (!Array.isArray(paths)) {\n throw new Error('Not an array');\n }\n } catch {\n // Fall back to comma-separated string\n paths = value.split(',').map(p => p.trim()).filter(p => p.length > 0);\n }\n \n // Normalize paths for consistent comparison\n const normalizedPaths = paths.map(normalizePath);\n \n // Validate that paths exist and are directories\n const isValid = normalizedPaths.every(path => {\n try {\n return existsSync(path) && statSync(path).isDirectory();\n } catch {\n return false;\n }\n });\n \n workspaces[name] = {\n paths: normalizedPaths,\n isValid,\n };\n }\n }\n \n return workspaces;\n}\n\nexport function getWorkspacePaths(config: Config, workspace: string): string[] {\n const workspaceConfig = config.workspaces[workspace];\n return workspaceConfig?.paths || [];\n}\n\nexport function getAvailableWorkspaces(config: Config): string[] {\n return Object.keys(config.workspaces);\n}\n\nexport function loadConfig(options: { verbose?: boolean } = {}): Config {\n const dataDir = process.env.DIRECTORY_INDEXER_DATA_DIR || join(homedir(), '.directory-indexer');\n \n // Use separate database file and collection for tests to avoid contaminating main data\n const isTest = process.env.NODE_ENV === 'test' || process.env.VITEST === 'true';\n const dbFileName = isTest ? 'test-data.db' : 'data.db';\n const defaultCollection = isTest ? 'directory-indexer-test' : 'directory-indexer';\n \n // Parse workspace configurations from environment variables\n const workspaces = parseWorkspaces(process.env);\n \n const config = {\n storage: {\n sqlitePath: join(dataDir, dbFileName),\n qdrantEndpoint: process.env.QDRANT_ENDPOINT || 'http://127.0.0.1:6333',\n qdrantCollection: process.env.DIRECTORY_INDEXER_QDRANT_COLLECTION || defaultCollection,\n qdrantApiKey: process.env.QDRANT_API_KEY,\n },\n embedding: {\n provider: (process.env.EMBEDDING_PROVIDER as Config['embedding']['provider']) || 'ollama',\n model: process.env.EMBEDDING_MODEL || 'nomic-embed-text',\n endpoint: process.env.OLLAMA_ENDPOINT || 'http://127.0.0.1:11434',\n },\n indexing: {\n chunkSize: parseInt(process.env.CHUNK_SIZE || '512'),\n chunkOverlap: parseInt(process.env.CHUNK_OVERLAP || '50'),\n maxFileSize: parseInt(process.env.MAX_FILE_SIZE || '10485760'),\n ignorePatterns: ['.git', 'node_modules', 'target', '.DS_Store'],\n respectGitignore: process.env.RESPECT_GITIGNORE !== 'false',\n },\n dataDir,\n verbose: options.verbose ?? (process.env.VERBOSE === 'true'),\n workspaces,\n };\n\n try {\n return ConfigSchema.parse(config);\n } catch (error) {\n if (error instanceof z.ZodError) {\n const messages = error.errors.map(e => `${e.path.join('.')}: ${e.message}`);\n throw new ConfigError(`Configuration validation failed: ${messages.join(', ')}`, error);\n }\n throw new ConfigError('Failed to load configuration', error as Error);\n }\n}"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config.js","sources":["../src/config.ts"],"sourcesContent":["import { homedir } from 'os';\nimport { join } from 'path';\nimport { existsSync, statSync } from 'fs';\nimport { z } from 'zod';\nimport { normalizePath } from './utils';\n\nconst WorkspaceSchema = z.object({\n paths: z.array(z.string()),\n isValid: z.boolean(),\n filesCount: z.number().optional(),\n chunksCount: z.number().optional(),\n});\n\nconst ConfigSchema = z.object({\n storage: z.object({\n sqlitePath: z.string(),\n qdrantEndpoint: z.url(),\n qdrantCollection: z.string(),\n qdrantApiKey: z.string().optional(),\n }),\n embedding: z.object({\n provider: z.enum(['ollama', 'openai', 'mock']),\n model: z.string(),\n endpoint: z.url(),\n }),\n indexing: z\n .object({\n chunkSize: z.number().positive(),\n chunkOverlap: z.number().nonnegative(),\n maxFileSize: z.number().positive(),\n ignorePatterns: z.array(z.string()),\n respectGitignore: z.boolean(),\n })\n .superRefine((indexing, ctx) => {\n if (indexing.chunkOverlap >= indexing.chunkSize) {\n ctx.addIssue({\n code: 'custom',\n path: ['chunkOverlap'],\n message: 'chunkOverlap must be smaller than chunkSize',\n });\n }\n }),\n dataDir: z.string(),\n verbose: z.boolean(),\n workspaces: z.record(z.string(), WorkspaceSchema),\n});\n\nexport type Config = z.infer<typeof ConfigSchema>;\nexport type WorkspaceConfig = z.infer<typeof WorkspaceSchema>;\n\nexport class ConfigError extends Error {\n constructor(message: string, public override cause?: Error) {\n super(message);\n this.name = 'ConfigError';\n }\n}\n\nfunction parseWorkspaces(env: Record<string, string | undefined>): Record<string, WorkspaceConfig> {\n const workspaces: Record<string, WorkspaceConfig> = {};\n \n for (const [key, value] of Object.entries(env)) {\n if (key.startsWith('WORKSPACE_') && value) {\n const name = key.replace('WORKSPACE_', '').toLowerCase();\n \n // Parse paths from comma-separated string or JSON array\n let paths: string[];\n try {\n // Try parsing as JSON array first\n paths = JSON.parse(value);\n if (!Array.isArray(paths)) {\n throw new Error('Not an array');\n }\n } catch {\n // Fall back to comma-separated string\n paths = value.split(',').map(p => p.trim()).filter(p => p.length > 0);\n }\n \n // Normalize paths for consistent comparison\n const normalizedPaths = paths.map(normalizePath);\n \n // Validate that paths exist and are directories\n const isValid = normalizedPaths.every(path => {\n try {\n return existsSync(path) && statSync(path).isDirectory();\n } catch {\n return false;\n }\n });\n \n workspaces[name] = {\n paths: normalizedPaths,\n isValid,\n };\n }\n }\n \n return workspaces;\n}\n\nexport function getWorkspacePaths(config: Config, workspace: string): string[] {\n const workspaceConfig = config.workspaces[workspace];\n return workspaceConfig?.paths || [];\n}\n\nexport function getAvailableWorkspaces(config: Config): string[] {\n return Object.keys(config.workspaces);\n}\n\nexport function loadConfig(options: { verbose?: boolean } = {}): Config {\n const dataDir = process.env.DIRECTORY_INDEXER_DATA_DIR || join(homedir(), '.directory-indexer');\n \n // Use separate database file and collection for tests to avoid contaminating main data\n const isTest = process.env.NODE_ENV === 'test' || process.env.VITEST === 'true';\n const dbFileName = isTest ? 'test-data.db' : 'data.db';\n const defaultCollection = isTest ? 'directory-indexer-test' : 'directory-indexer';\n \n // Parse workspace configurations from environment variables\n const workspaces = parseWorkspaces(process.env);\n \n const config = {\n storage: {\n sqlitePath: join(dataDir, dbFileName),\n qdrantEndpoint: process.env.QDRANT_ENDPOINT || 'http://127.0.0.1:6333',\n qdrantCollection: process.env.DIRECTORY_INDEXER_QDRANT_COLLECTION || defaultCollection,\n qdrantApiKey: process.env.QDRANT_API_KEY,\n },\n embedding: {\n provider: (process.env.EMBEDDING_PROVIDER as Config['embedding']['provider']) || 'ollama',\n model: process.env.EMBEDDING_MODEL || 'nomic-embed-text',\n endpoint: process.env.EMBEDDING_PROVIDER === 'openai'\n ? (process.env.OPENAI_ENDPOINT || 'https://api.openai.com/v1')\n : (process.env.OLLAMA_ENDPOINT || 'http://127.0.0.1:11434'),\n },\n indexing: {\n chunkSize: parseInt(process.env.CHUNK_SIZE || '512'),\n chunkOverlap: parseInt(process.env.CHUNK_OVERLAP || '50'),\n maxFileSize: parseInt(process.env.MAX_FILE_SIZE || '10485760'),\n ignorePatterns: ['.git', 'node_modules', 'target', '.DS_Store'],\n respectGitignore: process.env.RESPECT_GITIGNORE !== 'false',\n },\n dataDir,\n verbose: options.verbose ?? (process.env.VERBOSE === 'true'),\n workspaces,\n };\n\n try {\n return ConfigSchema.parse(config);\n } catch (error) {\n if (error instanceof z.ZodError) {\n const messages = error.issues.map(e => `${e.path.join('.')}: ${e.message}`);\n throw new ConfigError(`Configuration validation failed: ${messages.join(', ')}`, error);\n }\n throw new ConfigError('Failed to load configuration', error as Error);\n }\n}"],"names":[],"mappings":"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"}
package/dist/embedding.js CHANGED
@@ -86,7 +86,7 @@ class OpenAIEmbeddingProvider {
86
86
  dimensions = 1536;
87
87
  async generateEmbedding(text) {
88
88
  try {
89
- const response = await fetch("https://api.openai.com/v1/embeddings", {
89
+ const response = await fetch(`${this.config.endpoint}/embeddings`, {
90
90
  method: "POST",
91
91
  headers: {
92
92
  "Content-Type": "application/json",
@@ -108,7 +108,7 @@ class OpenAIEmbeddingProvider {
108
108
  }
109
109
  async generateEmbeddings(texts) {
110
110
  try {
111
- const response = await fetch("https://api.openai.com/v1/embeddings", {
111
+ const response = await fetch(`${this.config.endpoint}/embeddings`, {
112
112
  method: "POST",
113
113
  headers: {
114
114
  "Content-Type": "application/json",
@@ -1 +1 @@
1
- {"version":3,"file":"embedding.js","sources":["../src/embedding.ts"],"sourcesContent":["import { Config } from './config.js';\n\nexport interface EmbeddingProvider {\n name: string;\n dimensions: number;\n generateEmbedding(text: string): Promise<number[]>;\n generateEmbeddings(texts: string[]): Promise<number[][]>;\n}\n\nexport interface EmbeddingConfig {\n model: string;\n endpoint: string;\n dimensions: number;\n}\n\nexport class EmbeddingError extends Error {\n constructor(message: string, public override cause?: Error) {\n super(message);\n this.name = 'EmbeddingError';\n }\n}\n\nclass MockEmbeddingProvider implements EmbeddingProvider {\n name = 'mock';\n \n constructor(private config: EmbeddingConfig) {}\n \n get dimensions(): number {\n return this.config.dimensions;\n }\n \n async generateEmbedding(text: string): Promise<number[]> {\n const hash = this.hashString(text);\n return Array.from({ length: this.dimensions }, (_, i) => \n Math.sin(hash + i) * 0.1\n );\n }\n \n async generateEmbeddings(texts: string[]): Promise<number[][]> {\n return Promise.all(texts.map(text => this.generateEmbedding(text)));\n }\n \n private hashString(str: string): number {\n let hash = 0;\n for (let i = 0; i < str.length; i++) {\n const char = str.charCodeAt(i);\n hash = ((hash << 5) - hash) + char;\n hash = hash & hash;\n }\n return Math.abs(hash);\n }\n}\n\nclass OllamaEmbeddingProvider implements EmbeddingProvider {\n name = 'ollama';\n dimensions = 768;\n \n constructor(private config: EmbeddingConfig) {}\n \n async generateEmbedding(text: string): Promise<number[]> {\n try {\n const response = await fetch(`${this.config.endpoint}/api/embed`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n model: this.config.model,\n input: text\n })\n });\n \n if (!response.ok) {\n throw new Error(`Ollama API error: ${response.statusText}`);\n }\n \n const data = await response.json();\n return data.embeddings[0];\n } catch (error) {\n throw new EmbeddingError(`Failed to generate Ollama embedding`, error as Error);\n }\n }\n \n async generateEmbeddings(texts: string[]): Promise<number[][]> {\n try {\n const response = await fetch(`${this.config.endpoint}/api/embed`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n model: this.config.model,\n input: texts\n })\n });\n \n if (!response.ok) {\n throw new Error(`Ollama API error: ${response.statusText}`);\n }\n \n const data = await response.json();\n return data.embeddings;\n } catch (error) {\n throw new EmbeddingError(`Failed to generate Ollama embeddings`, error as Error);\n }\n }\n}\n\nclass OpenAIEmbeddingProvider implements EmbeddingProvider {\n name = 'openai';\n dimensions = 1536;\n \n constructor(private config: EmbeddingConfig) {}\n \n async generateEmbedding(text: string): Promise<number[]> {\n try {\n const response = await fetch('https://api.openai.com/v1/embeddings', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`\n },\n body: JSON.stringify({\n model: this.config.model,\n input: text\n })\n });\n \n if (!response.ok) {\n throw new Error(`OpenAI API error: ${response.statusText}`);\n }\n \n const data = await response.json();\n return data.data[0].embedding;\n } catch (error) {\n throw new EmbeddingError(`Failed to generate OpenAI embedding`, error as Error);\n }\n }\n \n async generateEmbeddings(texts: string[]): Promise<number[][]> {\n try {\n const response = await fetch('https://api.openai.com/v1/embeddings', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`\n },\n body: JSON.stringify({\n model: this.config.model,\n input: texts\n })\n });\n \n if (!response.ok) {\n throw new Error(`OpenAI API error: ${response.statusText}`);\n }\n \n const data = await response.json();\n return data.data.map((item: { embedding: number[] }) => item.embedding);\n } catch (error) {\n throw new EmbeddingError(`Failed to generate OpenAI embeddings`, error as Error);\n }\n }\n}\n\nexport function createEmbeddingProvider(provider: string, config: EmbeddingConfig): EmbeddingProvider {\n switch (provider) {\n case 'mock':\n return new MockEmbeddingProvider(config);\n case 'ollama':\n return new OllamaEmbeddingProvider(config);\n case 'openai':\n return new OpenAIEmbeddingProvider(config);\n default:\n throw new EmbeddingError(`Unknown embedding provider: ${provider}`);\n }\n}\n\nexport async function generateEmbedding(text: string, config: Config): Promise<number[]> {\n const provider = createEmbeddingProvider(config.embedding.provider, {\n model: config.embedding.model,\n endpoint: config.embedding.endpoint,\n dimensions: config.embedding.provider === 'mock' ? 384 : (config.embedding.provider === 'ollama' ? 768 : 1536)\n });\n \n return provider.generateEmbedding(text);\n}"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"embedding.js","sources":["../src/embedding.ts"],"sourcesContent":["import { Config } from './config.js';\n\nexport interface EmbeddingProvider {\n name: string;\n dimensions: number;\n generateEmbedding(text: string): Promise<number[]>;\n generateEmbeddings(texts: string[]): Promise<number[][]>;\n}\n\nexport interface EmbeddingConfig {\n model: string;\n endpoint: string;\n dimensions: number;\n}\n\nexport class EmbeddingError extends Error {\n constructor(message: string, public override cause?: Error) {\n super(message);\n this.name = 'EmbeddingError';\n }\n}\n\nclass MockEmbeddingProvider implements EmbeddingProvider {\n name = 'mock';\n \n constructor(private config: EmbeddingConfig) {}\n \n get dimensions(): number {\n return this.config.dimensions;\n }\n \n async generateEmbedding(text: string): Promise<number[]> {\n const hash = this.hashString(text);\n return Array.from({ length: this.dimensions }, (_, i) => \n Math.sin(hash + i) * 0.1\n );\n }\n \n async generateEmbeddings(texts: string[]): Promise<number[][]> {\n return Promise.all(texts.map(text => this.generateEmbedding(text)));\n }\n \n private hashString(str: string): number {\n let hash = 0;\n for (let i = 0; i < str.length; i++) {\n const char = str.charCodeAt(i);\n hash = ((hash << 5) - hash) + char;\n hash = hash & hash;\n }\n return Math.abs(hash);\n }\n}\n\nclass OllamaEmbeddingProvider implements EmbeddingProvider {\n name = 'ollama';\n dimensions = 768;\n \n constructor(private config: EmbeddingConfig) {}\n \n async generateEmbedding(text: string): Promise<number[]> {\n try {\n const response = await fetch(`${this.config.endpoint}/api/embed`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n model: this.config.model,\n input: text\n })\n });\n \n if (!response.ok) {\n throw new Error(`Ollama API error: ${response.statusText}`);\n }\n \n const data = await response.json();\n return data.embeddings[0];\n } catch (error) {\n throw new EmbeddingError(`Failed to generate Ollama embedding`, error as Error);\n }\n }\n \n async generateEmbeddings(texts: string[]): Promise<number[][]> {\n try {\n const response = await fetch(`${this.config.endpoint}/api/embed`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n model: this.config.model,\n input: texts\n })\n });\n \n if (!response.ok) {\n throw new Error(`Ollama API error: ${response.statusText}`);\n }\n \n const data = await response.json();\n return data.embeddings;\n } catch (error) {\n throw new EmbeddingError(`Failed to generate Ollama embeddings`, error as Error);\n }\n }\n}\n\nclass OpenAIEmbeddingProvider implements EmbeddingProvider {\n name = 'openai';\n dimensions = 1536;\n \n constructor(private config: EmbeddingConfig) {}\n \n async generateEmbedding(text: string): Promise<number[]> {\n try {\n const response = await fetch(`${this.config.endpoint}/embeddings`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`\n },\n body: JSON.stringify({\n model: this.config.model,\n input: text\n })\n });\n \n if (!response.ok) {\n throw new Error(`OpenAI API error: ${response.statusText}`);\n }\n \n const data = await response.json();\n return data.data[0].embedding;\n } catch (error) {\n throw new EmbeddingError(`Failed to generate OpenAI embedding`, error as Error);\n }\n }\n \n async generateEmbeddings(texts: string[]): Promise<number[][]> {\n try {\n const response = await fetch(`${this.config.endpoint}/embeddings`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`\n },\n body: JSON.stringify({\n model: this.config.model,\n input: texts\n })\n });\n \n if (!response.ok) {\n throw new Error(`OpenAI API error: ${response.statusText}`);\n }\n \n const data = await response.json();\n return data.data.map((item: { embedding: number[] }) => item.embedding);\n } catch (error) {\n throw new EmbeddingError(`Failed to generate OpenAI embeddings`, error as Error);\n }\n }\n}\n\nexport function createEmbeddingProvider(provider: string, config: EmbeddingConfig): EmbeddingProvider {\n switch (provider) {\n case 'mock':\n return new MockEmbeddingProvider(config);\n case 'ollama':\n return new OllamaEmbeddingProvider(config);\n case 'openai':\n return new OpenAIEmbeddingProvider(config);\n default:\n throw new EmbeddingError(`Unknown embedding provider: ${provider}`);\n }\n}\n\nexport async function generateEmbedding(text: string, config: Config): Promise<number[]> {\n const provider = createEmbeddingProvider(config.embedding.provider, {\n model: config.embedding.model,\n endpoint: config.embedding.endpoint,\n dimensions: config.embedding.provider === 'mock' ? 384 : (config.embedding.provider === 'ollama' ? 768 : 1536)\n });\n \n return provider.generateEmbedding(text);\n}"],"names":[],"mappings":"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"}
package/dist/indexing.js CHANGED
@@ -1,9 +1,10 @@
1
1
  import { promises } from "fs";
2
+ import { createHash } from "node:crypto";
2
3
  import { join } from "path";
3
4
  import { getFileInfo, normalizePath, shouldIgnoreFile, isDirectory, isFile, isSupportedFileType } from "./utils.js";
4
5
  import { loadGitignoreRules } from "./gitignore.js";
5
6
  import { generateEmbedding } from "./embedding.js";
6
- import { initializeStorage } from "./storage.js";
7
+ import { initializeStorage, ensureCollectionDimensions } from "./storage.js";
7
8
  import { log } from "./logger.js";
8
9
  class IndexingError extends Error {
9
10
  constructor(message, cause) {
@@ -12,6 +13,10 @@ class IndexingError extends Error {
12
13
  this.name = "IndexingError";
13
14
  }
14
15
  }
16
+ function pointIdFor(filePath, chunkId) {
17
+ const hex = createHash("sha256").update(`${filePath}:${chunkId}`).digest("hex").slice(0, 32);
18
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
19
+ }
15
20
  function chunkText(content, chunkSize, overlap) {
16
21
  if (content.length <= chunkSize) {
17
22
  return [{
@@ -51,10 +56,16 @@ async function scanDirectory(dirPath, options) {
51
56
  const gitignoreFilter = options.respectGitignore ? await loadGitignoreRules(dirPath) : null;
52
57
  async function walkDirectory(currentPath) {
53
58
  const normalizedPath = normalizePath(currentPath);
54
- if (visited.has(normalizedPath)) {
59
+ let realPath = normalizedPath;
60
+ try {
61
+ realPath = normalizePath(await promises.realpath(normalizedPath));
62
+ } catch {
63
+ realPath = normalizedPath;
64
+ }
65
+ if (visited.has(realPath)) {
55
66
  return;
56
67
  }
57
- visited.add(normalizedPath);
68
+ visited.add(realPath);
58
69
  try {
59
70
  const relativePath = normalizedPath.startsWith(basePath) ? normalizedPath.slice(basePath.length + 1) : normalizedPath;
60
71
  if (shouldIgnoreFile(normalizedPath, relativePath, options.ignorePatterns, gitignoreFilter)) {
@@ -96,31 +107,38 @@ async function shouldReprocessFile(filePath, existingRecord, config) {
96
107
  const fs2 = await import("fs/promises");
97
108
  const currentStats = await fs2.stat(filePath);
98
109
  const existingModTime = new Date(existingRecord.modifiedTime);
99
- if (currentStats.mtime <= existingModTime) {
110
+ if (currentStats.mtime.getTime() === existingModTime.getTime()) {
100
111
  return false;
101
112
  }
102
113
  const currentFileInfo = await getFileInfo(filePath);
103
114
  return currentFileInfo.hash !== existingRecord.hash;
104
115
  } catch (modtimeError) {
105
116
  if (config.verbose) {
106
- console.log(`Warning: Could not check modification time for ${filePath}:`, modtimeError);
117
+ log("warning", `Could not check modification time for ${filePath}`, {
118
+ error: modtimeError instanceof Error ? modtimeError.message : String(modtimeError)
119
+ });
107
120
  }
108
121
  try {
109
122
  const currentFileInfo = await getFileInfo(filePath);
110
123
  return currentFileInfo.hash !== existingRecord.hash;
111
124
  } catch (hashError) {
112
125
  if (config.verbose) {
113
- console.log(`Warning: Could not compute hash for ${filePath}:`, hashError);
126
+ log("warning", `Could not compute hash for ${filePath}`, {
127
+ error: hashError instanceof Error ? hashError.message : String(hashError)
128
+ });
114
129
  }
115
130
  return true;
116
131
  }
117
132
  }
118
133
  }
119
- async function indexDirectories(paths, config) {
134
+ async function indexDirectories(paths, config, reporters = {}) {
135
+ const progress = reporters.progress ?? ((message) => log("info", message));
136
+ const reportError = reporters.error ?? ((message) => log("error", message));
120
137
  let indexed = 0;
121
138
  let skipped = 0;
122
139
  let failed = 0;
123
140
  let deleted = 0;
141
+ let collectionEnsured = false;
124
142
  const errors = [];
125
143
  const scanOptions = {
126
144
  ignorePatterns: config.indexing.ignorePatterns,
@@ -132,19 +150,19 @@ async function indexDirectories(paths, config) {
132
150
  for (const path of paths) {
133
151
  try {
134
152
  if (config.verbose) {
135
- console.log(`Scanning directory: ${path}`);
153
+ progress(`Scanning directory: ${path}`);
136
154
  }
137
155
  const files = await scanDirectory(path, scanOptions);
138
156
  totalFiles += files.length;
139
157
  if (config.verbose) {
140
- console.log(`Found ${files.length} files to process in ${path}`);
158
+ progress(`Found ${files.length} files to process in ${path}`);
141
159
  }
142
160
  } catch {
143
161
  }
144
162
  }
145
163
  if (!config.verbose && totalFiles > 0) {
146
- console.log("");
147
- console.log(`Found ${totalFiles} files to process (checking for changes...)`);
164
+ progress("");
165
+ progress(`Found ${totalFiles} files to process (checking for changes...)`);
148
166
  }
149
167
  for (const path of paths) {
150
168
  try {
@@ -153,19 +171,22 @@ async function indexDirectories(paths, config) {
153
171
  const files = await scanDirectory(path, scanOptions);
154
172
  const dirStartIndexed = indexed;
155
173
  const dirStartSkipped = skipped;
174
+ let dirHadFailure = false;
156
175
  const progressInterval = totalFiles > 1e3 ? 50 : 10;
157
176
  for (const file of files) {
177
+ let chunks = [];
158
178
  try {
159
179
  const existingFile = await sqlite.getFile(file.path);
160
180
  if (existingFile) {
161
- const needsReprocessing = await shouldReprocessFile(file.path, existingFile, config);
181
+ const hasStoredFailure = existingFile.errors?.some((error) => error.startsWith("Failed to process")) ?? false;
182
+ const needsReprocessing = hasStoredFailure || await shouldReprocessFile(file.path, existingFile, config);
162
183
  if (!needsReprocessing) {
163
184
  skipped++;
164
185
  if (config.verbose) {
165
- console.log(` Skipped: ${file.path} (unchanged)`);
186
+ progress(` Skipped: ${file.path} (unchanged)`);
166
187
  }
167
188
  if (!config.verbose && (indexed + skipped) % progressInterval === 0) {
168
- console.log(`Progress: ${indexed + skipped}/${totalFiles} files (${skipped} skipped as unchanged)...`);
189
+ progress(`Progress: ${indexed + skipped}/${totalFiles} files (${skipped} skipped as unchanged)...`);
169
190
  }
170
191
  continue;
171
192
  }
@@ -179,15 +200,17 @@ async function indexDirectories(paths, config) {
179
200
  continue;
180
201
  }
181
202
  const content = rawContent.replace(/\r\n/g, "\n");
182
- const chunks = chunkText(content, config.indexing.chunkSize, config.indexing.chunkOverlap);
203
+ chunks = chunkText(content, config.indexing.chunkSize, config.indexing.chunkOverlap);
183
204
  await sqlite.upsertFile(file, chunks);
184
205
  for (let i = 0; i < chunks.length; i++) {
185
206
  const chunk = chunks[i];
186
207
  const embedding = await generateEmbedding(chunk.content, config);
187
- const hashNum = parseInt(file.hash.slice(0, 8), 16);
188
- const pointId = hashNum % 1e6 * 1e3 + parseInt(chunk.id);
208
+ if (!collectionEnsured) {
209
+ await ensureCollectionDimensions(sqlite, qdrant, config, embedding.length);
210
+ collectionEnsured = true;
211
+ }
189
212
  const point = {
190
- id: pointId,
213
+ id: pointIdFor(file.path, chunk.id),
191
214
  vector: embedding,
192
215
  payload: {
193
216
  filePath: file.path,
@@ -201,10 +224,10 @@ async function indexDirectories(paths, config) {
201
224
  }
202
225
  indexed++;
203
226
  if (config.verbose) {
204
- console.log(` Indexed: ${file.path} (${chunks.length} chunks)`);
227
+ progress(` Indexed: ${file.path} (${chunks.length} chunks)`);
205
228
  }
206
229
  if (!config.verbose && (indexed + skipped) % progressInterval === 0) {
207
- console.log(`Progress: ${indexed + skipped}/${totalFiles} files (${skipped} skipped as unchanged)...`);
230
+ progress(`Progress: ${indexed + skipped}/${totalFiles} files (${skipped} skipped as unchanged)...`);
208
231
  }
209
232
  } catch (error) {
210
233
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -212,7 +235,9 @@ async function indexDirectories(paths, config) {
212
235
  const fullError = `Failed to process ${file.path}: ${errorMessage}${causeMessage}`;
213
236
  errors.push(fullError);
214
237
  failed++;
215
- console.error(`❌ ${fullError}`);
238
+ dirHadFailure = true;
239
+ reportError(`❌ ${fullError}`);
240
+ await sqlite.upsertFile(file, chunks, [fullError]);
216
241
  }
217
242
  }
218
243
  const indexedFiles = await sqlite.getFilesByDirectory(normalizedPath);
@@ -224,26 +249,26 @@ async function indexDirectories(paths, config) {
224
249
  await sqlite.deleteFile(deletedFile.path);
225
250
  deleted++;
226
251
  if (config.verbose) {
227
- console.log(` Cleaned up deleted file: ${deletedFile.path}`);
252
+ progress(` Cleaned up deleted file: ${deletedFile.path}`);
228
253
  }
229
254
  } catch (error) {
230
255
  const errorMessage = error instanceof Error ? error.message : String(error);
231
256
  const fullError = `Failed to clean up deleted file ${deletedFile.path}: ${errorMessage}`;
232
257
  errors.push(fullError);
233
258
  failed++;
234
- console.error(`❌ ${fullError}`);
259
+ dirHadFailure = true;
260
+ reportError(`❌ ${fullError}`);
235
261
  }
236
262
  }
237
- const directoryErrors = errors.filter((err) => err.includes(path));
238
- const directoryStatus = directoryErrors.length > 0 ? "failed" : "completed";
263
+ const directoryStatus = dirHadFailure ? "failed" : "completed";
239
264
  await sqlite.upsertDirectory(normalizedPath, directoryStatus);
240
265
  const dirFiles = files.length;
241
266
  const dirIndexed = indexed - dirStartIndexed;
242
267
  const dirSkipped = skipped - dirStartSkipped;
243
268
  if (config.verbose) {
244
- console.log(`Directory ${path} completed: ${dirIndexed} indexed, ${dirSkipped} skipped`);
269
+ progress(`Directory ${path} completed: ${dirIndexed} indexed, ${dirSkipped} skipped`);
245
270
  } else {
246
- console.log(`Directory ${path} completed: ${dirFiles} files processed`);
271
+ progress(`Directory ${path} completed: ${dirFiles} files processed`);
247
272
  }
248
273
  } catch (error) {
249
274
  const normalizedPath = normalizePath(path);
@@ -258,6 +283,7 @@ export {
258
283
  chunkText,
259
284
  getFileMetadata,
260
285
  indexDirectories,
286
+ pointIdFor,
261
287
  scanDirectory
262
288
  };
263
289
  //# sourceMappingURL=indexing.js.map