Primo Explore setup and usage guide
Primo Explore is a Python library for building interactive code browsers that let you navigate software visually. It wraps around ctags or tree-sitter parsers and turns symbol databases into a web interface you can click through. It runs locally, which matters if you're dealing with proprietary codebases and don't want to ship source to a third-party service. The installation is straightforward but there are a few dependencies that trip people up. Install it with pip first. Then you need ctags on your system path. On macOS that's brew install universal-ctags. On Ubuntu it's sudo apt install ctags but make sure you get the universal-ctags build, not the old GNU one, because the tag format matters for Primo to pick up classes and methods correctly. The library itself is at github.com/jeanncarlaus/primo.
Primo Explore walkthrough for real projects
Here is the practical flow. You point it at a directory, it generates a tags database, then spins up a local server. A single command does both steps. You open the URL it prints and you see a tree of symbols with clickable references. The index step is where most people run into trouble because their project structure is nonstandard. Primo expects a flat or conventional hierarchy. If you have a project where source files are scattered across nested directories without any convention, the parser drops a lot of symbols. I hit this exact problem when working with a large React project that split components across at least seven subdirectories in a pattern that didn't match any preset. The initial index only captured about 40% of the classes and hooks I expected to see. The workaround was to add a custom tags file configuration and explicitly list the directories in the .primo config rather than relying on the auto-discovery logic. Once I did that, coverage jumped to roughly 95%, which is about as good as you get without switching tools entirely. There are things beginners miss about how Primo actually uses the symbol data. The first is that Primo does not index imported modules by default. If you're browsing a Node project and clicking a utility function navigates nowhere because that function lives in a package in node_modules, you need to either add those packages to the index manually or point Primo at them as external sources. I spent an afternoon trying to figure out why imports from my shared utilities library weren't linking until I realized Primo just wasn't looking there. The second thing is that the generated HTML is static but the search is client-side JavaScript, so large codebases with over fifty thousand symbols start to feel sluggish in the browser. That is a hard limitation of the architecture. If you are dealing with something that size, consider generating search indices for only the parts you care about, or pairing Primo with a dedicated search tool like grep or ripgrep for the initial discovery phase.
The web interface has a sidebar with file trees and a main panel that shows symbol details. You can filter by kind, search by regex, and jump to definition. It works well for quick navigation during debugging sessions. The export feature is useful if you need to share a code map with someone who doesn't want to install anything. It generates a standalone HTML file with the index embedded. Configuration lives in a .primo file or config.json in your project root. You can specify parser overrides, exclude patterns, and output directories. The exclude patterns use glob syntax. If your project has build artifacts or test fixtures cluttering the namespace, add them early or the indexer takes longer than it needs to and you get noise in your results. For TypeScript projects, Primo uses the TypeScript language server under the hood for symbol extraction, which is generally accurate but slower than ctags for plain Python or Go. I measure that difference as roughly three to five times slower on medium-sized codebases. If speed matters more than precision on types, you can fall back to ctags with a TypeScript grammar pack, but you lose some type-level information. There is no clean middle ground unless you are willing to run both pipelines in parallel and merge the outputs, which is tedious to set up and maintain.
Get the Full Details
Primo Explore also supports multiple output formats beyond the web UI. You can export to JSON, markdown, or SVG diagrams. The SVG export is handy for documentation because it generates a visual graph of how modules relate. The diagrams get dense quickly past a couple hundred nodes though, so I usually restrict the scope to specific subsystems rather than the entire codebase. One more thing nobody mentions upfront: Primo does not handle incremental updates well. Every time you change code, you need to regenerate the full index. There is no delta indexing. For a small project that rebuilds in seconds that is fine. For a large monorepo where reindexing takes twenty minutes, you feel that pain every time you switch contexts. Some teams work around this by running the indexer in the background on a schedule and pointing the UI at the cached index, but that introduces its own staleness problems. You need to decide what tradeoff makes sense for your workflow. If you need something more performant for extremely large repos or real-time collaborative browsing, you might look at tools built on LSP servers directly or commercial alternatives, but those come with costs or setup overhead that Primo avoids. Primo sits in a useful middle ground for developers who want a free, self-hosted option without becoming a full search infrastructure project.