npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@trusz/topos

v0.1.0

Published

Topos — CLI that visualizes cross-file import graphs for TypeScript/React/Vue/Svelte projects as a self-contained interactive HTML graph.

Readme

Topos

A CLI that scans a TypeScript / React / Vue / Svelte project, resolves what every file imports, and emits a single self-contained interactive HTML graph. Files are boxes; folders are boxes that contain their children. Import relationships are the edges.

Click any folder to collapse it: its subtree folds into one node, internal imports disappear, and the imports crossing its boundary become weighted, directional aggregated edges (e.g. module1 ──2──▶ module2). Click again to expand.

Each box also shows coupling metrics:

  • ↓<incoming> ↑<outgoing> — for a folder, the import edges crossing its subtree boundary (independent of collapse state).
  • I <decimal>instability (Martin's metric) = out / (in + out): 0 = stable (depended upon, depends on little), 1 = unstable (depends on much, nothing depends on it). Shown to two decimals, e.g. I 0.25; I – when the box has no edges.

A type-only imports checkbox in the toolbar shows/hides edges that are exclusively import type (edges that also carry a runtime import stay). Toggling it updates the edges, the counts, and the instability metric together.

Layouts

A Layout dropdown in the toolbar (powered by ELK plus fCoSE):

  • ELK: Layered (default) — hierarchical, edges flow top-down by dependency direction. Best for reading "what imports what".
  • ELK: Stress — distances reflect coupling; good for spotting clusters.
  • ELK: Force / Radial / Tree and fCoSE (organic).

An Edges dropdown switches edge routing — Curved (bezier), Straight, Step (orthogonal), or Smooth step (rounded orthogonal). Purely visual; applied instantly.

Layout runs when the graph's structure changes — on first load, when you open/close a folder, on Collapse all / Expand all, when you pick a layout, or via the Re-layout button. Dragging a node never triggers a layout: it stays where you put it and nothing else moves.

Install

Homebrew

brew install trusz/tap/topos

From source

npm install
npm run build

Releasing (maintainer)

./build.sh builds and packages topos-<version>.tgz (printing its sha256); ./publish.sh tags the commit, creates the GitHub release with that tarball, and runs npm publish (ships dist only). Then bump topos.rb (repo root, master) in trusz/homebrew-tap with the new url + sha256.

Users can then install via Homebrew (above) or npm install -g @trusz/topos.

Usage

topos <root> [options]

  -o, --out <file>      output HTML file (default: topos.html)
  --tsconfig <file>     path to tsconfig.json (default: nearest)
  --no-gitignore        do not honor .gitignore
  -e, --exclude <glob>  glob to exclude from scanning; repeatable or comma-separated,
                        e.g. -e '**/*.test.ts' -e '**/*.spec.ts' -e '**/*.config.*'
  --kinds <list>        import kinds to include
                        (static,reexport,sideeffect,type,dynamic,require;
                         default: static,reexport,sideeffect,type)
  --json                machine-readable JSON (with --metrics), or write the graph
                        model next to the HTML
  --tree                print an ASCII tree with in/out/instability metrics to
                        stdout (no HTML is written)
  --depth <n>           max tree depth to print with --tree (default: unlimited)
  -m, --metrics <path>  print in/out/instability for a file or folder (repeatable,
                        comma-separated); stdout, no HTML. Combine with --json.

Tree output

topos ./src --tree prints the project as an ASCII tree. Every file and folder is annotated with ↓ <incoming> ↑ <outgoing> I <instability>, where folder values are the imports crossing that folder's subtree boundary:

.
├── module2/ ↓ 3 ↑ 2 I 0,4000
│   ├── module2.ts ↓ 3 ↑ 2 I 0,4000
│   └── side.ts ↓ 1 ↑ 0 I 0,0000
└── main.ts ↓ 0 ↑ 4 I 1,0000

Limit the printed depth with --depth <n> (folder aggregates still reflect the full subtree, only the printing is truncated).

Metrics for specific paths

Query individual files/folders without rendering the whole tree:

$ topos ./src -m src/module2 -m src/main.ts
src/module2     ↓ 3 ↑ 2 I 0,4000
src/main.ts     ↓ 0 ↑ 4 I 1,0000

$ topos ./src --metrics src/module2 --json
[ { "path": "src/module2", "id": "src/module2", "found": true,
    "incoming": 3, "outgoing": 2, "instability": 0.4 } ]

Paths may be given relative to the cwd or to the scan root. Unknown paths are reported on stderr (and found: false in JSON).

Exclude globs are matched relative to <root>. node_modules is always skipped. Type-only imports (import type { T } from './t') are included by default; drop them with --kinds static,reexport,sideeffect.

Open the resulting HTML file in any browser — it is fully offline.

Try the example

npm run example        # writes example/graph.html from example/sample

How it works

A small pipeline with a stable JSON graph model in the middle:

discover → resolve-config → extract-imports → resolve-specifiers → build-graph → render-html
  • discover — walk the root, honor .gitignore, skip node_modules; scan .ts .tsx .js .jsx .mjs .cjs .vue .svelte.
  • resolve-config — read tsconfig.json (paths/baseUrl) and vite.config.* (resolve.alias, parsed statically) into one alias map.
  • extract-imports — per-framework unwrappers pull script text from .vue (@vue/compiler-sfc) and .svelte (svelte/compiler); a shared ts-morph pass reads import / re-export / side-effect specifiers.
  • resolve-specifiers — map each specifier to a scanned file via alias / relative / index resolution; drop externals and non-script targets; dedupe into weighted edges.
  • build-graph — file nodes + folder (compound) nodes + edges → GraphModel JSON.
  • render-html — inline the model and the bundled Cytoscape viewer into one HTML file.

See CONTEXT.md for terminology and docs/adr/ for the key design decisions.