Skip to content
| Marketplace
Sign in
Visual Studio Code>Visualization>depphunterNew to Visual Studio Code? Get it now.
depphunter

depphunter

Dawid Ciepiela

|
4 installs
| (1) | Free
Browse the workspace as an interactive isometric city, in the editor's built-in browser.
Installation
Launch VS Code Quick Open (Ctrl+P), paste the following command, and press enter.
Copied to clipboard
More Info

depphunter

CI

Note: This codebase was developed with the assistance of AI tools (Claude, by Anthropic). All code is reviewed and tested before being merged.

Initial view Dependency trace
Initial view Dependency trace
Walk mode Night mode
Walk mode Night mode
Flight mode Vulnerability hunting
Flight mode Vulnerability hunting

Browse any code base as an interactive isometric archipelago in your browser.

  • Mainland = your repository. Directories are terraces, files are buildings (height = lines of code, color = language). The map is drawn as a city: facades with windows, streets with sidewalks and crossings between buildings, ramps between levels, parks with trees, all in a rippling sea; when a selection dims the rest, dimmed buildings turn plain so the focus stands out.
  • Islands = external ecosystems (Go modules, the standard library, …) with one building per dependency.
  • Click anything to see what it depends on and what uses it: arcs over the map to each one, and the same edges laid down as roads through the streets, with chevrons for the direction the dependency runs in. A road to an external package leaves the shore as a causeway. Double-click to expand or collapse directories and files (files expand into their symbols).
  • Hunt dependencies on foot (V): walk the map in first person on a tiny planet, WASD to move, the mouse to look, Space to jump, F to fly. You hold a tool - a fishing rod by default - drawn in your hands, on the end of an arm, and using it (click) on a building tags the module, lights up its dependency trails and plants a beacon over it. The rest are in slots 1…7, the way a shooter holds them, and T walks along the row: a butterfly net, a camera, a bubble wand, a tracking dart, a nail gun and a grapple gun. Each swings its own way, brings its own aim helper, and is good for its own quarry
    • the rod, the dart and the nailer work on buildings, the net and the bubbles on bugs, the camera on either. What they throw flies as what it is: a nail goes fast and flat, a dart drops like a dart, a bubble slows and climbs. The net throws nothing at all and has to be walked up to; the camera's back has the view through its lens live on it. Two of them pull on their line once it has stuck: the rod hauls you in to the wall it hooked, and the grapple gun, which does nothing else to what it hits, up the facade and onto the roof - and from up there a shot over the edge is the way down. Hold the right button for the scope, the mouse wheel zooms. Bugs are one per finding a scanner reported, and they are not only on the streets: a building is walked on at several heights up its facade and around its roof, each beetle standing out of whatever it is holding on to, and some of them are not holding on to anything - they fly a circuit round the building, rising and falling and leaning into the corners, wings going. Catching one opens what was said about it; a tracker in the corner sweeps the map around you, and grows and closes in as you come up on one, so the last few steps can be taken on the sweep rather than by guesswork. Terraces become city blocks: the space between buildings is a connected street network with sidewalks, lane markings and crossings, ramps and stairs lead between levels, empty lots are parks with trees and bushes, and bridges cross the water to every island.

Everything runs locally: one binary, no Node.js, and no network access unless you ask for it with --online (see Package indexes).

Two ways to run it

depphunter is a command-line tool that serves the map to your browser. The VS Code extension is a thin wrapper around that same tool: it starts it for the folder you are working in and shows the map in a tab beside the code. The map itself is the same page either way.

Command-line tool VS Code extension
Install a release archive, or go install the .vsix from a release, plus the command-line tool
Start depphunter [path] in a terminal depphunter: Open the Map, or right-click a folder
The map opens in your browser the editor's built-in browser, beside the code
Configure with flags, DEPPHUNTER_* variables, .depphunter.yaml depphunter.* settings, and the same .depphunter.yaml
Also does exports to JSON, GraphML, DOT and HTML one server per folder, kept running while you work

Contents

  • Command-line tool: Install · Usage · Configuration · Watch mode and cache · Exports · Opening files in your editor
  • VS Code extension: Install · Use · Settings · Remote workspaces · How the framing works
  • The map: Keyboard & mouse · Styles · Findings · Git history · Versions and pinning · Dependencies of dependencies · Package indexes · CI pipelines · Symbol references · Languages
  • Security · Contributing · License

Command-line tool

Install

Download an archive for your platform from the releases (checksums in checksums.txt), or build it with Go 1.27.1 or newer:

OS Architectures Archive
Linux amd64, arm64, armv7, 386, riscv64 .tar.gz
macOS amd64 (Intel), arm64 (Apple silicon) .tar.gz
Windows amd64, arm64, 386 .zip
FreeBSD amd64, arm64 .tar.gz
go install github.com/sarumaj/depphunter-cli/cmd/depphunter@latest

Usage

depphunter            # analyze the current directory and open the browser
depphunter ~/src/app  # analyze another directory
depphunter --no-open --addr 127.0.0.1:8080
depphunter --watch    # keep the map in sync while you edit
depphunter --findings trivy.json      # put what a scanner reported on the map
depphunter --export dot -o deps.dot   # write the graph and exit
depphunter --export html -o map.html  # a self-contained map to share
Flag Default
--addr 127.0.0.1:0 listen address; port 0 picks a free port
--no-open print the URL instead of opening the browser
--exclude glob of paths to skip (repeatable)
--max-file-size 2097152 larger files are listed but not read
--config <path>/.depphunter.yaml config file to use
--theme auto auto, light, dark
--color-by language language, size, commits, churn, age, authors
--height-scale sqrt linear, sqrt, log
--style city what the map is dressed as: city, circuit, galaxy
--show-std false show standard-library islands
--expand-depth 0 initially expanded depth; 0 = auto, -1 = all
--watch false re-analyze on file changes, update the browser live
--no-cache neither read nor write the analysis cache
--no-history do not read git history
--history-commits 10000 read at most this many commits
--resolve-depth 0 levels of dependencies-of-dependencies from lock files (-1 = all)
--online false ask package indexes for what the project's files do not record
--lsp find symbol references with installed language servers
--lsp-timeout 5m time budget for language servers
--findings scanner report to place on the map (repeatable, globs)
--no-vulns place no findings, and do not ask the OSV database
-v, --version print the version and exit
-h, --help list the flags with their defaults
--editor auto-detected editor command template, e.g. "code -g {file}:{line}"
--embed origin allowed to show the map in a frame, e.g. vscode-webview: (repeatable)
--export write json, graphml, dot or html and exit
-o, --output stdout output file for --export

Long flags take two dashes (--addr, not -addr); a flag's value may follow after a space or =.

Configuration

Settings are resolved from, in increasing precedence: built-in defaults, the user config ($XDG_CONFIG_HOME/depphunter/config.yaml, or the OS equivalent), the project config .depphunter.yaml, DEPPHUNTER_* environment variables (ADDR, OPEN, EXCLUDE, MAX_FILE_SIZE, THEME, COLOR_BY, HEIGHT_SCALE, SHOW_STD, EXPAND_DEPTH, WATCH, CACHE, EDITOR, HISTORY, HISTORY_COMMITS, RESOLVE_DEPTH, ONLINE, LSP, LSP_TIMEOUT), and flags. Exclude globs add up across all sources instead of replacing each other. The project config cannot set editor: it arrives with the repository, and the editor is a command depphunter runs.

# .depphunter.yaml
exclude: [testdata, "*.pb.go"]
history_commits: 5000
ui:
  color_by: language
  height_scale: sqrt
  show_std: false
  expand_depth: 0
  tool: rod                       # walk mode: rod, net, camera, bubbles, dart
  hide_languages: [Markdown]      # filters, as the Filters panel sets them
  hide_islands: [npm]
  path_filter: "!**/testdata/**"

The browser's Save settings button writes the current color, height, theme, depth and filters into the ui: section of .depphunter.yaml (or the --config file), keeping the file's other keys and comments.

Watch mode and cache

Parsing results are cached per file content under the user cache directory (~/.cache/depphunter on Linux), so a second run only parses files that changed: the CPython standard library goes from 1.2 s to 20 ms. With --watch, depphunter watches the directories it analyzed, re-analyzes after changes settle (300 ms), and pushes the new map to the browser, which keeps your expansion, selection and filters and briefly highlights the files that changed.

Exports

--export (or the Export menu in the browser) writes:

Format Contents
json the full graph document the UI uses (nodes, symbols, edges)
graphml the full graph with all attributes, for Gephi, yEd or NetworkX
dot the dependency graph for Graphviz: files, package directories and external packages, clustered per directory; standard-library packages and files without imports are left out
html the interactive map as one file that opens without depphunter or a network: UI, graph, source text (files up to 256 KB, 24 MB in total) and the view you are looking at — colors, height, theme, depth and filters (the CLI's --export html uses the configured view)

Export → PNG image (or P) saves the map as shown, labels included, at your screen's resolution; it also works in an exported HTML page.

The HTML export contains your source code and, when the git history was read, commit authors' names; share it like you would share the repository.

Dependency graphs are shallow, so Graphviz draws them long and thin; for big ones, unflatten -l 3 -c 5 deps.dot | dot -Tsvg -o deps.svg spreads them out.

Opening files in your editor

The side panel's Open in editor button (or O) opens the selected file at the selected symbol's line. depphunter uses --editor / DEPPHUNTER_EDITOR / the user config, or detects a GUI editor from $VISUAL, $EDITOR or PATH (VS Code, Cursor, Zed, Sublime Text, JetBrains IDEs, …). Without one, the button hands the file to VS Code's vscode:// URL handler. In VS Code, the extension points it at the editor it is running in.

VS Code extension

The extension puts the map in a tab beside the code. It starts depphunter for the folder you are working in, waits for it to say where it is listening, and opens that address in the editor's built-in browser; the map behaves exactly as it does in a browser tab, live updates and all. Its source is in extension/.

Install the extension

It is not on a marketplace yet, but every release carries one .vsix per platform, each with the binary for that platform inside it. Take the one that matches - linux-x64, darwin-arm64, win32-x64 and so on - and pick it from Extensions: Install from VSIX… in the command palette, or from a terminal:

code --install-extension depphunter_1.2.3_vscode_darwin-arm64.vsix

Nothing else to install. The extension and the server it starts come out of the same release and carry the same version, so they cannot drift apart. There is a _universal build as well, for a platform not on the list; that one has no binary and falls back to depphunter on PATH.

To run a different build - one you are working on, or a newer release on a machine whose extension has not been updated - set depphunter.path to it. An explicit setting always wins over the one that shipped.

Use

Click the depphunter icon in the activity bar: the Maps view lists the window's folders, and clicking one maps it. Its buttons restart and stop a running server, and the view's title bar has the log and the settings. The same is in the command palette as depphunter: Open the Map, and on the explorer's right-click menu for any folder.

Command What it does
depphunter: Open the Map Maps the folder, or shows the map already made
depphunter: Restart the Server Starts it again, which is how settings take effect
depphunter: Stop the Server Stops it; the next open analyzes afresh
depphunter: Show the Server Log The server's own output, verbatim
depphunter: Open Settings The extension's settings, below

One server per folder, kept until the window closes or you stop it: analyzing a large repository takes a moment, and with --watch it only has to happen once. A status bar item appears while one is running, and clicking it opens the map.

Settings

Settings, in the groups the Settings editor shows them in. Each one names the flag it passes, and passes it only when set to something other than what depphunter would do anyway - an enum at default, a number left empty, a switch where depphunter's own default puts it - so a folder's .depphunter.yaml still decides everything you leave alone here.

Setting Default Flag What it does
General
depphunter.path (empty) The binary to run. Empty means the one the extension ships with, falling back to depphunter on PATH.
depphunter.openIn webview webview (a tab of its own), simpleBrowser (the built-in browser) or externalBrowser (yours).
depphunter.watch true --watch Re-analyze on file changes and update the map.
depphunter.config "" --config A config file to use instead of the folder's .depphunter.yaml, relative to the folder.
depphunter.editorCommand "" --editor What Open in editor runs. Empty means this editor.
depphunter.args [] Further arguments, one per entry, passed last. Usage has the list.
Analysis
depphunter.exclude [] --exclude Globs of paths to skip.
depphunter.maxFileSize (empty) --max-file-size Files larger than this many bytes are not read.
depphunter.resolveDepth (empty) --resolve-depth Levels of dependencies-of-dependencies to resolve from lock files; -1 for all.
depphunter.online false --online Ask package indexes, and the OSV database, over the network.
depphunter.cache true --no-cache Read and write the analysis cache.
depphunter.history true --no-history Read git history for the history overlays.
depphunter.historyCommits (empty) --history-commits Read at most this many commits.
Appearance
depphunter.style default --style city, circuit or galaxy.
depphunter.theme default --theme auto, light or dark.
depphunter.colorBy default --color-by language, size, commits, churn, age or authors.
depphunter.heightScale default --height-scale linear, sqrt or log.
depphunter.expandDepth (empty) --expand-depth Directory levels expanded at first; 0 picks, -1 expands everything.
depphunter.showStd false --show-std Show standard-library islands.
Findings
depphunter.findings [] --findings Scanner reports to place on the map, relative to the folder. Globs allowed.
depphunter.vulns true --no-vulns Place reports on the map and, with online, ask the OSV database.
References
depphunter.lsp false --lsp Find symbol references with installed language servers.
depphunter.lspTimeout "" --lsp-timeout Time budget for the language servers, e.g. 90s.

What is left out is left out on purpose: --addr, --no-open and --embed are how the extension hosts the map and are not for changing, and --export writes a file and exits rather than serving.

They are ordinary settings, so they can be set per workspace in .vscode/settings.json:

{
  "depphunter.style": "circuit",
  "depphunter.findings": ["reports/trivy.json", "reports/*.sarif"],
  "depphunter.exclude": ["vendor"],
  "depphunter.lsp": true,
  "depphunter.resolveDepth": 1
}

A server reads its settings when it starts, so changing one takes effect on the next depphunter: Restart the Server - the extension offers to do it for you. Anything the settings do not cover goes in depphunter.args, or in the project's .depphunter.yaml, which the server reads as usual.

Open in editor opens the file at the line you were looking at, in this editor: the extension works out where this editor's own command-line launcher is and hands it to the server, rather than leaving it to find whatever is on the PATH the extension host inherited. depphunter.editorCommand overrides that when you want the file somewhere else.

Remote workspaces

Over SSH, WSL and dev containers the port is forwarded to localhost on your machine and everything works. In Codespaces and on vscode.dev the forwarded address is a public hostname, which the server rejects as a DNS-rebinding attempt: it only answers to localhost and 127.0.0.1. Run depphunter in a terminal there instead, for now.

Why the map is in a tab of its own

The editor's built-in browser is the obvious place to put a local page, and it was the first thing this used. It puts that page in a sandbox of its own, though, and one of the things that sandbox withholds is the pointer lock - so walk mode could not capture the mouse there, and a sandbox can only ever be narrowed on the way down, so there was nothing the page could do about it.

The map opens in a tab of the editor's own instead: one webview, one iframe, and that iframe carries no sandbox attribute - which takes nothing away, and so keeps the pointer lock the editor granted the webview in the first place. What is lost is an address bar and a back button, on a single page that needs neither. depphunter.openIn can still ask for simpleBrowser, and walk mode there says so and turns the view by dragging instead.

How the framing works

Either way the page is inside a webview, which means it is framed by origins belonging to the editor. depphunter refuses to be framed by default, so the extension starts it with --embed, naming every frame above the page - all three of them, because frame-ancestors is checked against the whole chain and not just the frame holding the page:

--embed vscode-webview: --embed vscode-file: --embed https://*.vscode-cdn.net
  • vscode-webview: is the webview, which is given an origin of its own for every session (vscode-webview://<uuid>), so there is no exact name to give.
  • vscode-file: is the editor's window, served from vscode-file://vscode-app in the desktop editor.
  • https://*.vscode-cdn.net is the webview in the browser build.

Leave one of them out and the browser refuses the page before it loads: an empty tab, and the reason only in the webview's own developer tools. Nothing else may frame it - a page at any other origin is refused the same way. What else that mode changes is in Inside an editor.

In that mode the session token stays in the address instead of being exchanged for a cookie, because a cookie set by the map would be a third-party cookie inside the frame and would never be sent back. This is why the extension reads the address out of the server's own output rather than putting it together from the port: that address is the only place the token appears.

The map

Everything below is the map itself, and works the same whether the command-line tool or the extension started it. Where a section names a flag, the extension takes it through its own setting where there is one (depphunter.findings, depphunter.style, depphunter.watch) and through depphunter.args otherwise.

Keyboard & mouse

Panning stops once the center of the view is a quarter of the map's size beyond its edge, and zooming out once the map covers about a third of the view; in walk mode you can go 3 units out over the water and 12 above the tallest building.

Drag / right-drag / wheel pan / orbit / zoom
Click / double-click select / expand–collapse
Enter, Backspace expand–collapse selection, select parent
→ ← in the panel open / close a dependency row
Enter while reading close the details and walk on
T in walk mode take out another tool
Q E rotate 90°
Home fit to screen
+ − expand / collapse one level everywhere
/ search files, symbols and packages
O open the selected file in your editor
P save the map as a PNG image
Legend click hide / show a language
Pin click read the findings over a building
+ beside a finding put it in the backpack
B the backpack: everything caught
The figure where you were standing in walk mode
Esc close the backpack, or clear selection
V walk mode

In walk mode:

Mouse look; captured at the reticle (Esc frees it, a click on the map captures it again), or drag. Where the mouse cannot be captured at all - a frame that withholds the pointer lock - walk mode says so once and a click uses the tool instead of asking again
1 … 7 pick a tool by its slot; T still walks along the row row
W A S D/arrows move / turn; Shift runs
Space jump (flying: straight up)
F fly on / off; flying, W/S move where you look (look down and press W to dive), C goes straight down
Click use what is in your hands: the module it reaches is tagged, a bug it catches is read out and kept
H put the tool away, or take it out again; it still works, and throws from your eye
Hold right button look through the scope
Enter details of what the reticle is on, like a second dart (frees the mouse; click the map to walk on)
Wheel zoom in / out
+ - (or [ ]) planet size (curvature)
V / Esc back to the map; going in again puts you back where you stood

The map draws the walker where they are standing, as a figure facing the way they were facing, and going back in puts them there - unless you picked something on the map while you were away, which is how you say "take me there" instead.

On foot, the shore stops you, but every island can be reached over a bridge; flying, you can go 3 units out over the water. The ground you stand on is never a target, so aiming at the street does not select it. Expanding and collapsing is left to the map view: it rebuilds the whole city, which is disorienting from street level. The key list folds away once you start moving; ? shows all controls.

Styles

The same map, dressed three ways (--style, or the Style menu):

Style What it is
city Buildings with facades and roofs, streets with crossings and parks, shores with trees, bridges between the islands
circuit A printed circuit board: chip packages with rows of pins, heatsinks where the buildings are tall, copper traces down every street with vias along them, solder pads and silkscreen around every part, capacitors where the trees were and LEDs, lit, where the lamps were. Off the edge of the board is the backplane it is plugged into, and it is live: charge runs along its tracks, which is this style's way of saying you cannot walk there
galaxy Platforms out in the dark: crystal spires with strata of light and windows like stars, glowing conduits between them, and instead of sea and sky the band of the galaxy with its dust lanes, two nebulae behind it and three layers of stars in front. The void the platforms hang in drifts, in layers and at three speeds, so the map view keeps drawing itself while this style is on; the other two are still

Only the environment changes. The colors that carry data - the language palette, the history overlays, hover and selection - are the same in all three, so a style is a look and never a different reading of the code. Nothing else changes either: the same layout, the same streets, the same walk.

Findings

depphunter does not run a scanner; it reads what yours already wrote. Point --findings at the JSON your CI produces (the flag is repeatable and takes globs) and each report lands on the map:

Tool written by
govulncheck -format json the advisory, and the call site that reaches it
npm audit --json npm 7+ and the older npm 6 advisory table
trivy … --format json vulnerabilities, misconfigurations and secret hits
osv-scanner --format json lock-file scans
golangci-lint run --out-format json one finding per issue
eslint -f json one finding per message

The format is recognized from the report's own shape, not from its file name, so it does not matter what your pipeline calls them:

govulncheck -format json ./... > reports/govulncheck.json
trivy fs --format json -o reports/trivy.json .
depphunter --findings 'reports/*.json'

A finding is placed where it belongs: a vulnerability on the package it affects, a linter's complaint on the file it is about, and a directory carries the worst of everything below it. Severities are one scale - critical, high, medium, low, info - taken from the advisory's CVSS v3 vector where there is one, because the word a distribution chose often disagrees with it (Trivy's MEDIUM for CVE-2020-8203 is a 9.8 vector). A linter's "error" is deliberately not a critical advisory: lint findings are capped at medium, or the streets would fill with bugs that mean a missing comment.

With --online, depphunter additionally asks OSV about every external package the map pins to a version - one batched query for the whole dependency tree, then the advisories it matched - for Go, npm, PyPI, crates.io, Maven, NuGet and GitHub Actions. Floating packages are not asked: they resolve to something else on the next install. Answers are cached for six hours. --no-vulns turns all of it off.

Seen from above, every building that carries findings wears a pin, in the color of the worst of them and standing taller the more there are: from across the map the red ones are where to go next. Point at one for the tally, click it to read them. The side panel lists them under Findings, worst first, each opening in place for the description, the fixed version and the advisory link, and a collapsed directory opens what is below it as well - a district is red for something several levels down, and that is where it can be reached from.

In walk mode they come out on the streets: every finding is a bug patrolling the building it belongs to, colored by severity, and catching one with whatever tool is in your hands - the butterfly net was made for this - opens what it was carrying. The HUD counts how many are left.

The backpack (B) is what the two views share. Taking a finding with the + beside it in the panel is the same catch as netting its bug in the street - the bug stops walking either way - and what is in there survives a relayout, a depth change and a reload. It stays until the scanners stop reporting it, and is then struck through rather than dropped, so seeing what you caught turn green is the point of having caught it.

A repository is bigger than it looks from inside it, so the corner of the walk HUD carries a tracker: a sweep centered on you and turning with you, with a dot per bug in its severity's color, a ring per module you have already tagged, and an arrow on the rim for anything beyond its range. Its range follows the hunt - it fits whatever is still out there - and under it is how far the nearest bug is and what it is carrying.

Git history

In a git work tree, depphunter reads the history of the analyzed files (the newest 10,000 non-merge commits by default; --history-commits changes the limit, --no-history turns it off) in the background once the map is shown, and caches it per commit. The color menu then offers:

Mode color shows
Commits commits per file (per-file mean for collapsed directories)
Lines changed lines added plus deleted
Last change how recently a file changed, recent is strong
Authors distinct authors

Files without commits in range get a separate neutral color. The Since slider in the legend limits commits, lines changed and authors to a time range; tooltips and the side panel show the same figures, the panel also the top authors. Renamed files keep the history of their old names. With --watch, a new commit updates the overlay.

Versions and pinning

Every external package carries the version the project resolves it to, and whether anything fixes it there. Lock files, exact specifiers (==1.2.3, RequiredVersion), single-version ranges ([1.2.3]), commits and digests pin a dependency; ranges, wildcards, snapshots and moving tags let it drift. A dependency nothing pins is drawn in amber, badged ⚠ floating in the side panel, and marked in its tooltip; where a lock file resolved a range, the panel shows both - 4.3.1, requested as ^4.2.0.

Ecosystem pinned by floats on
Go modules the version in go.mod, which the build picks a require without a version
npm package-lock.json, yarn.lock, pnpm-lock.yaml, an exact 1.2.3 any range - including 1.2, which means 1.2.x
crates.io Cargo.lock the manifest alone: "1.2.3" there means ^1.2.3
PyPI poetry.lock, uv.lock, pdm.lock, Pipfile.lock, ==1.2.3 >=, ~=, ^, or no version at all
Maven a plain version, [1.2.3] ranges, LATEST, RELEASE, -SNAPSHOT, unexpanded ${…}
NuGet an exact version, [1.2.3] wildcards (2.*) and ranges
PowerShell Gallery RequiredVersion ModuleVersion, which is a minimum

The JSON and GraphML exports carry requested and floating per package.

Dependencies of dependencies

--resolve-depth walks past what your code imports into what those packages themselves pull in: 1 adds one level, 2 two, -1 as far as the answer reaches. The answer comes from the lock files the repository already carries - nothing is fetched, and depphunter stays offline.

Lock file gives
package-lock.json (v1-v3) every installed package and what it requires
pnpm-lock.yaml (v5-v9) packages: and, since v9, snapshots:
yarn.lock (classic) each entry's resolved version and dependencies
Cargo.lock dependencies per crate
uv.lock, poetry.lock, pdm.lock each distribution's own requirements

Packages that arrive this way are marked transitive - no file here imports them - and the edges between packages are depends, apart from the import edges that start at a file, so "imported by N files" keeps meaning what it says. Two versions of one package are one building, as they always were, so an edge between packages is an edge between names.

Ecosystems whose lock files carry no edges (Pipfile.lock) add nothing here; those that keep the graph outside the repository (Go modules, and Maven, NuGet and the PowerShell Gallery for now) need --online, below.

The side panel reads them as a tree: every row under Depends on and Used by opens into what that node depends on in turn, and so on down. Nothing is fetched - the edges are already in the map - so a row opens instantly, ▸/▾ or the arrow keys open and close it, and what you opened stays open when a --watch update redraws the panel. A package that depends on something that depends back on it is shown once more with ↻ and left closed, because lock files do contain cycles and a tree that followed one would never end.

Package indexes

Every external package says where it comes from. depphunter reads the index configuration your machine has and the one the repository carries - .npmrc (including @scope:registry), .yarnrc.yml, pip.conf and a requirements file's --index-url, Poetry and uv sources in pyproject.toml, NuGet.config, a pom's <repositories>, ~/.m2/settings.xml mirrors, .cargo/config.toml, and GOPROXY - and the side panel names the index each package resolves from. A container image needs no configuration: ghcr.io/org/app says it already.

The two are not treated alike. An index your machine names is trusted; one that appears only in the repository is recorded and marked ⚠ index, because a repository that points your package manager at an index nobody here configured is the shape a dependency-confusion attack takes. Nothing is ever fetched from such an index.

--online lets depphunter ask the trusted indexes about dependencies the repository does not record - which is how --resolve-depth reaches the ecosystems whose graph lives outside the repo:

Ecosystem asked for answer
Go <proxy>/<module>/@v/<version>.mod that module's own requires
npm <registry>/<package>/<version> its dependencies
PyPI <host>/pypi/<name>/<version>/json requires_dist, extras excluded

Lock files still come first: an index is asked only where the repository is silent. Answers are cached for a day under the cache directory, credentials come from your own ~/.npmrc tokens and ~/.netrc and are sent only to the host they were written for, and Maven, NuGet and container registries are not asked yet.

CI pipelines

The code that runs with your repository's secrets is a dependency too, and it is declared nowhere a package manager looks. depphunter reads it from the pipeline files themselves - .github/workflows/*.yml, action.yml, .gitlab-ci.yml, *.gitlab-ci.yml and .gitlab/** - and puts it on the map beside the packages:

  • GitHub Actions: each step's uses:, reusable workflows (jobs.<id>.uses), and a composite action's own steps. A ./path resolves to the action.yml or workflow inside this repository, so a local action's own dependencies chain on.
  • GitLab CI: every include: form - local, project (with ref and file), template, remote and component - plus the includes a bridge job triggers.
  • Container images: container:, services:, docker://… and a Docker action's runs.image on GitHub; image:, services: and default: on GitLab, per job and pipeline-wide.

Jobs become the symbols of their file, so a pipeline expands into its jobs the way a source file expands into its functions.

Pinning is stricter here than in a package ecosystem, because a reference that can be rewritten is not a pin: only a commit or a digest counts. actions/checkout@v4 floats - the tag can be moved to other code - and so does nginx:1.25.3, since a tag is republished whenever its owner likes. The hardening convention of pinning to a commit and naming the version in a comment is read as both: actions/setup-go@3041bf5… # v5.0.1 shows the commit as the version and v5.0.1 as what was requested. A GitLab template or a remote include names no version at all and still changes under you, so it is floating as well.

Symbol references

Imports show which files depend on which; with --lsp, depphunter also asks language servers which symbols use which. It uses the servers it finds on PATH (and go install locations for gopls):

Language Server
Go gopls
JavaScript / TypeScript typescript-language-server
Python pyright-langserver, basedpyright-langserver or pylsp
Rust rust-analyzer
Java jdtls
C# csharp-ls

The servers run in the background after the map is shown (gopls needs about 7 s for this repository), within --lsp-timeout; results are cached until the code changes. The legend's Imports / References switch then changes what selection arcs and the side panel show: for a function, what it uses and what uses it. JSON and GraphML exports include the reference edges.

Languages

Ecosystem Imports resolved through Islands
Go every go.mod (multi-module, local replace) Go modules, Go standard library
JavaScript / TypeScript relative paths, tsconfig/jsconfig paths, workspaces, package.json + package-lock.json / yarn.lock / pnpm-lock.yaml npm, Node.js built-ins
Python relative imports, src/ layouts, requirements files, setup.cfg, literal setup.py lists, pyproject.toml, Pipfile, poetry.lock/uv.lock/pdm.lock/Pipfile.lock PyPI, Python standard library
Rust the module tree (crate::, self::, super::, mod x;), workspace and path crates, Cargo.toml (renamed and workspace dependencies) + Cargo.lock crates.io, Rust standard library
Java source files by package path (any source root), pom.xml (properties, dependency management), Gradle scripts and version catalogs Maven, Java standard library
C# namespaces to project folders (RootNamespace + folder), PackageReference, Directory.Packages.props NuGet, .NET base library
PowerShell using module, Import-Module, dot-sourced and &-invoked scripts ($PSScriptRoot), #Requires -Modules, module manifests (RequiredModules, RootModule, NestedModules) PowerShell Gallery, built-in modules
CI pipelines GitHub workflows and composite actions (uses:, reusable workflows, container:, services:), GitLab pipelines (every include: form, components, image:, services:) GitHub Actions, GitLab CI, Container images

Java imports name packages, not artifacts, so they are matched to Maven groupIds by prefix, shared leading segments, artifact names and a short table of well-known mismatches (Guava, JUnit 4, Lombok, …); what cannot be matched is shown as unresolved.

Files in other languages appear on the map without dependency edges. Parsing uses a pure-Go tree-sitter runtime (C# and PowerShell use small built-in scanners instead), so the binary still cross-compiles without a C toolchain.

Security

The server binds to loopback by default and prints a URL containing a random token, which the browser exchanges for a cookie. Requests without it, requests with a foreign Host header (DNS rebinding), and requests for files that are not part of the analyzed project are rejected. Nothing may put the map in a frame: X-Frame-Options: DENY and frame-ancestors 'none'.

Inside an editor

That last part is also what stops an editor showing the map in its own built-in browser, which is a frame like any other. --embed <origin> allows the origins it names, and only those, which is what a wrapper such as an editor extension passes when it starts the server - every frame above the page, since frame-ancestors is checked against the whole chain and not just the parent (see How the framing works). Three things follow from it, and nothing else changes:

  • frame-ancestors names those origins instead of 'none', and X-Frame-Options is not sent, because it has no way to name an origin that browsers still honour.
  • The token stays in the address instead of being exchanged for a cookie. A cookie set by the map is a third-party cookie inside somebody else's frame, and browsers do not send those back; the page reads the token from its own URL and returns it on every call, in a header, or in the query string for the event stream, which cannot set headers. Referrer-Policy: no-referrer keeps it out of any request that leaves.
  • The interface's own files - app.js and what it imports - are served without the token, since a frame cannot attach one to a <script src>. They are the same bytes in every release and say nothing about the project. Everything under /api, and the document that hands the page its token, still needs it.

It is deliberately a flag and nothing else: no config file and no environment variable can turn it on, so a repository cannot arrange to be framed by a page of its choosing. Each origin is checked before it reaches the header, so nothing passed on the command line can end the directive early or start another one.

Contributing

Building, testing, how the code is laid out and how to work on the VS Code extension are in CONTRIBUTING.md.

License

BSD 3-Clause © 2026 Dawid Ciepiela. The embedded three.js (MIT), highlight.js (BSD 3-Clause), potpack (ISC) and fzf-for-js (BSD 3-Clause) keep their own licenses; see web/static/vendor.

  • Contact us
  • Jobs
  • Privacy
  • Manage cookies
  • Terms of use
  • Trademarks
  • Your Privacy Choices
  • Consumer Health Privacy
© 2026 Microsoft