Vyctor
A fast CLI tool for semantic file search using vector embeddings.
Features
- Semantic Search: Find files using natural language queries
- AST-Aware Chunking: Code is split at function/class boundaries for better results
- Fast Indexing: Parallel processing with incremental updates
- Multiple Embedding Providers: OpenAI, Voyage AI, or local models
- Optional Reranking: Second-stage ranking with Voyage AI for improved precision
- File Watching: Auto-sync on file changes with background daemon
- Index Browser: Inspect indexed files and chunks for debugging
- Local Storage: DuckDB with HNSW vector index for fast similarity search
Installation
Prerequisites
- Rust 1.70+ (install via rustup)
- DuckDB VSS extension (automatically installed on first run)
Build from source
# Clone the repository
# Build release binary
# The binary will be at target/release/vyctor
# Optionally, install it
With local embeddings support
Quick Start
# Initialize vyctor in your project
# Set your API key (or use local embeddings which require no key)
# You can export it or add it to a .env file in your project
# Search for files
# Search in a specific folder
# Get more results
# Show full content instead of preview
Commands
vyctor init
Initialize vyctor in the current directory. Creates vyctor.config.toml and starts initial indexing.
vyctor lookup
Search for files matching a natural language query.
vyctor sync
Synchronize the index with current files.
vyctor watch
Watch for file changes and automatically sync. Can run in foreground or as a background daemon.
# Foreground mode (Ctrl+C to stop)
# Daemon mode (runs in background) - macOS/Linux only
Note: Daemon mode (
--daemon) is supported on macOS and Linux. Windows users should use foreground mode (vyctor watch).
vyctor status
Show index status and statistics.
vyctor config
Show or edit configuration.
vyctor browse
Browse and analyze indexed files and chunks. Useful for debugging and understanding how your code is being indexed.
# List indexed files
# Browse chunks
# Index statistics
Note: The browse commands require exclusive access to the database. If the watcher daemon is running, stop it first with
vyctor watch --stop.
Configuration
The configuration file is stored at vyctor.config.toml in the project root (tracked by git).
Environment Variables: Vyctor automatically loads .env and .env.local files from your project directory, so you can store API keys there instead of exporting them.
[]
# Glob patterns for files to include
= ["**/*.rs", "**/*.ts", "**/*.py", "**/*.md"]
# Glob patterns for files to exclude
= ["**/node_modules/**", "**/target/**", "**/.git/**"]
# Chunk size in characters
= 1000
# Overlap between chunks
= 200
# AST-aware semantic chunking (splits at function/class boundaries)
= true
# Maximum chunk size before splitting large functions/classes
= 3000
[]
# Provider: "openai", "voyage", or "local"
= "local"
# Embedding dimensions (must match your model)
= 384
# Batch size for API requests
= 100
[]
= "sentence-transformers/all-MiniLM-L6-v2"
[]
= "text-embedding-3-small"
= "OPENAI_API_KEY"
[]
= "voyage-3-lite"
= "VOYAGE_API_KEY"
[]
# Optional second-stage ranking for better results
# Provider: "voyage" or "none" (disabled)
= "none"
= 30 # Candidates to retrieve before reranking
[]
= "VOYAGE_API_KEY"
= "rerank-2"
[]
# Auto-start daemon when running init/sync/lookup
= false
# Debounce interval in milliseconds
= 300
Embedding Providers
Local (default)
No API key needed. Models are downloaded from HuggingFace on first use.
[]
= "local"
= 384
[]
= "sentence-transformers/all-MiniLM-L6-v2"
OpenAI
[]
= "openai"
= 1536
[]
= "text-embedding-3-small" # or "text-embedding-3-large" (3072 dims)
= "OPENAI_API_KEY"
Voyage AI
[]
= "voyage"
= 512
[]
= "voyage-3-lite" # or "voyage-code-3" (1024 dims)
= "VOYAGE_API_KEY"
How It Works
-
Indexing: Vyctor walks your directory, reads matching files, and splits them into semantic chunks.
-
Embedding: Each chunk is converted to a vector embedding using your configured provider.
-
Storage: Embeddings are stored in DuckDB with an HNSW index for fast similarity search.
-
Search: Your query is embedded and compared against all chunks using cosine similarity.
-
Reranking (optional): Results are reranked using a cross-encoder model for improved relevance.
-
Incremental Updates: File hashes are tracked so only changed files are re-indexed.
Semantic Chunking
By default, vyctor uses AST-aware chunking to split code at meaningful boundaries:
- Functions, classes, and methods are kept together as single chunks
- Tree-sitter is used to parse code and identify semantic boundaries
- Fallback to regex patterns when tree-sitter isn't available for a language
- Large functions are split with the signature preserved in each sub-chunk
This produces much better search results than naive character-based splitting, which often cuts through the middle of functions.
Supported languages for AST parsing: Rust, TypeScript, JavaScript, Python, Go, Java, C, C++
[]
# Enable/disable semantic chunking (default: true)
= true
# Maximum size before splitting large functions (default: 3000)
= 3000
Reranking
For improved search quality, you can enable a reranker that performs a second-stage ranking of results:
[]
= "voyage" # Enable Voyage AI reranker
= 30 # Retrieve 30 candidates, then rerank to return top N
[]
= "VOYAGE_API_KEY"
= "rerank-2"
The reranker uses a cross-encoder model that scores query-document pairs more accurately than vector similarity alone. This is especially useful for:
- Complex queries with multiple concepts
- Finding exact matches within semantically similar results
- Improving precision when you need the most relevant results
Performance Tips
- Use larger
batch_sizefor faster initial indexing (but more memory) - Exclude large generated files and dependencies
- Use
vyctor watch --daemoninstead of frequentvyctor sync - For large codebases, consider using
text-embedding-3-smallfor speed
Auto-Start Daemon
Enable auto_start in your config to automatically start the watcher daemon:
[]
= true
= 300
With this enabled:
vyctor initstarts the daemon after initial indexingvyctor syncstarts the daemon after syncingvyctor lookupstarts the daemon before searching (if not already running)
This ensures your index stays fresh without manually running vyctor watch.
Each project gets its own independent daemon. Use vyctor watch --status to check if it's running.
License
MIT