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

@4n6h4x0r/shrinkpath

v0.1.0

Published

Smart cross-platform path shortening for CLIs, prompts, and tools

Readme

shrinkpath

Smart cross-platform path shortening for JavaScript and TypeScript.

npm CI License: MIT OR Apache-2.0

/Users/john/Library/Application Support/Code/User/settings.json  (62 chars)
                            ↓  shrinkTo(path, 35)
             /Users/john/.../User/settings.json                   (35 chars)

Filename is never truncated. Username is preserved when possible. Everything else adapts to fit your target length.

TypeScript port of the shrinkpath Rust crate. Full feature parity, zero dependencies.

Why shrinkpath?

  • Target-length guaranteeshrinkTo(path, 30) always returns ≤ 30 chars (unless the filename itself is longer — filenames are sacred)
  • Identity-preserving — the username/profile segment is the last thing to go, not the first
  • Cross-platform — handles /home/..., ~/..., C:\Users\..., \\server\share\..., .\... from any host OS
  • Zero dependencies — pure TypeScript, nothing to install beyond this package
  • No filesystem access — works on path strings alone (opt-in @4n6h4x0r/shrinkpath/fs subpath for filesystem-aware features)
  • Dual ESM/CJS — works in Node.js, Bun, bundlers, and anywhere JavaScript runs

Install

npm install @4n6h4x0r/shrinkpath

Quick Start

import { shrinkTo, shrinkFish } from '@4n6h4x0r/shrinkpath';

// Hybrid strategy: graduated shortening to fit a target length
const short = shrinkTo('/home/john/projects/rust/myapp/src/lib.rs', 30);
// => '/home/john/.../src/lib.rs' (≤ 30 chars, ends with lib.rs)

// Fish strategy: every directory becomes its first character
const fish = shrinkFish('/home/john/projects/rust/myapp/src/lib.rs');
// => '/h/j/p/r/m/s/lib.rs'

Strategies

shrinkpath ships four strategies. Pick the one that fits your use case, or use Hybrid (the default) and let the algorithm decide.

Fish

Abbreviates every directory segment to its first character. Dot-prefixed directories keep the dot: .config.c.

/home/john/projects/rust/myapp/src/lib.rs  →  /h/j/p/r/m/s/lib.rs
C:\Users\Admin\AppData\Local\Temp\file.txt →  C:\U\A\A\L\T\file.txt
~/projects/rust/file.rs                    →  ~/p/r/file.rs

Fish produces the shortest possible result. Use it when every character counts (prompts, status bars) and the user can infer the full path from context.

Tuning knobs:

import { shrink } from '@4n6h4x0r/shrinkpath';

// Keep 2 chars per segment instead of 1
shrink('/home/john/projects/rust/myapp/src/lib.rs', {
  maxLen: 50,
  strategy: 'fish',
  dirLength: 2,
});
// => '/ho/jo/pr/ru/my/sr/lib.rs'

// Keep the last N directory segments unabbreviated
shrink('/home/john/projects/rust/myapp/src/lib.rs', {
  maxLen: 50,
  strategy: 'fish',
  fullLengthDirs: 1,
});
// => '/h/j/p/r/m/src/lib.rs'

Ellipsis

Replaces middle segments with ..., keeping the identity head (username) and the segments nearest the filename.

/home/john/projects/rust/myapp/src/lib.rs  →  /home/john/.../src/lib.rs
C:\Users\Admin\AppData\Local\Temp\file.txt →  C:\Users\Admin\...\file.txt

Ellipsis is the most readable strategy. Use it when you have moderate space and want humans to immediately understand the path.

Hybrid (default)

A graduated four-phase approach that produces the best result for any target length:

Phase 1 — fish expendable segments only:    /home/john/p/r/m/src/lib.rs
Phase 2 — fish context segments too:        /h/john/p/r/m/s/lib.rs
Phase 3 — collapse abbreviated runs to ...: /h/john/.../s/lib.rs
Phase 4 — fish identity (last resort):      /h/j/.../s/lib.rs

Each phase stops as soon as the result fits. If nothing fits, it falls back to /.../<filename>, then the filename alone.

Unique

Disambiguates segments against each other within the same path. Each segment is abbreviated to the shortest prefix that distinguishes it from every other segment.

/home/documents/downloads/file.txt  →  /h/doc/dow/file.txt
/Users/Admin/AppData/Application/f  →  /U/Ad/AppD/Appl/f

When all first characters are unique, Unique behaves like Fish. When segments share prefixes, it uses the minimum characters needed. Identical segments are kept in full (they can't be disambiguated).

import { shrinkUnique } from '@4n6h4x0r/shrinkpath';

shrinkUnique('/home/documents/downloads/file.txt');
// => '/h/doc/dow/file.txt'

Features

Mapped Locations

Substitute known path prefixes before shortening. Useful for replacing home directories, project roots, or well-known paths with short aliases.

import { shrink } from '@4n6h4x0r/shrinkpath';

const result = shrink('/home/john/projects/rust/lib.rs', {
  maxLen: 50,
  mappedLocations: [
    ['/home/john', '~'],
    ['/home/john/projects', 'PROJ:'],
  ],
});
// Longest match wins → 'PROJ:/rust/lib.rs'

Anchor Segments

Mark directory names that should never be abbreviated, regardless of strategy.

import { shrink } from '@4n6h4x0r/shrinkpath';

const result = shrink('/home/john/projects/rust/myapp/src/lib.rs', {
  maxLen: 50,
  strategy: 'fish',
  anchors: ['src', 'myapp'],
});
// 'myapp' and 'src' kept full, everything else abbreviated
// => '/h/j/p/r/myapp/src/lib.rs'

Segment Metadata

shrinkDetailed() returns per-segment metadata for building colored prompts, clickable breadcrumbs, or tooltip UIs.

import { shrinkDetailed } from '@4n6h4x0r/shrinkpath';

const result = shrinkDetailed('/home/john/projects/lib.rs', {
  maxLen: Infinity,
  strategy: 'fish',
});

for (const seg of result.segments) {
  if (seg.wasAbbreviated) {
    // render abbreviated segments in dim color
    process.stdout.write(`\x1b[2m${seg.shortened}\x1b[0m/`);
  } else if (seg.isFilename) {
    // render filename in bold
    process.stdout.write(`\x1b[1m${seg.shortened}\x1b[0m`);
  } else {
    process.stdout.write(`${seg.shortened}/`);
  }
  // seg.original always contains the full text for tooltips
}

Filesystem-Aware Features

Import from @4n6h4x0r/shrinkpath/fs for features that require filesystem access (Node.js only):

import { findGitRoot, disambiguateSegment } from '@4n6h4x0r/shrinkpath/fs';

// Find the git repository name for a file path
const repo = findGitRoot('/home/john/projects/myapp/src/lib.rs');
// => 'myapp'

// Find shortest unique prefix by checking sibling directories
// If /home contains "documents", "downloads", "desktop":
const short = disambiguateSegment('/home', 'documents');
// => 'doc'

API

Convenience Functions

import { shrinkTo, shrinkFish, shrinkEllipsis, shrinkUnique } from '@4n6h4x0r/shrinkpath';

shrinkTo(path, 30)          // Hybrid strategy, target length
shrinkFish(path)            // Fish abbreviation, no length target
shrinkEllipsis(path, 30)    // Ellipsis strategy, target length
shrinkUnique(path)          // Unique disambiguation, no length target

Full Options

import { shrink } from '@4n6h4x0r/shrinkpath';
import type { ShrinkOptions } from '@4n6h4x0r/shrinkpath';

const result = shrink(path, {
  maxLen: 30,                                 // target max length
  strategy: 'ellipsis',                       // 'hybrid' | 'fish' | 'ellipsis' | 'unique'
  pathStyle: 'windows',                       // 'unix' | 'windows' (auto-detected by default)
  ellipsis: '..',                             // custom ellipsis marker
  dirLength: 2,                               // chars per abbreviated segment (fish/hybrid)
  fullLengthDirs: 1,                          // keep last N dirs unabbreviated (fish)
  anchors: ['src'],                           // never abbreviate these segments
  mappedLocations: [['/home/john', '~']],     // prefix substitutions
});

Detailed Result

import { shrinkDetailed } from '@4n6h4x0r/shrinkpath';
import type { ShrinkResult, SegmentInfo } from '@4n6h4x0r/shrinkpath';

const result: ShrinkResult = shrinkDetailed(path, { maxLen: 30 });

result.shortened      // the shortened path string
result.originalLen    // original path length
result.shortenedLen   // shortened path length
result.wasTruncated   // whether the path was modified
result.detectedStyle  // 'unix' | 'windows'
result.segments       // SegmentInfo[] with per-segment metadata

Types

import type {
  Strategy,       // 'hybrid' | 'fish' | 'ellipsis' | 'unique'
  PathStyle,      // 'unix' | 'windows'
  ShrinkOptions,  // options object
  ShrinkResult,   // detailed result
  SegmentInfo,    // per-segment metadata
} from '@4n6h4x0r/shrinkpath';

Platform Support

| Path format | Example | Detected as | |---|---|---| | Unix absolute | /home/john/file.rs | Unix | | Tilde home | ~/projects/file.rs | Unix | | macOS /Users | /Users/john/Documents/file.txt | Unix | | Windows drive | C:\Users\Admin\file.txt | Windows | | Windows UNC | \\server\share\dept\file.xlsx | Windows | | Dot-relative | .\src\main.rs | Windows | | Forward-slash drive | C:/Users/Admin/file.txt | Windows | | Relative (no prefix) | src/lib.rs | Unix | | Backslash heuristic | Users\Admin\file.txt | Windows |

Detection is automatic. Use pathStyle to override.

How It Works

Every input path is parsed into three parts:

  prefix       segments (directories)         filename
    │          │                                │
    ▼          ▼                                ▼
    /    home / john / projects / rust / src /  lib.rs
   ~~   ~~~~~~~~~~~~~~~~~~~~~~~~~~────────────  ~~~~~~

Each segment is classified by importance:

| Priority | What | Example | Dropped | |---|---|---|---| | Sacred | Filename | lib.rs | Never | | Identity | Username / profile | john, Admin | Last | | Context | Home root | home, Users | Middle | | Expendable | Everything else | projects, src | First |

Identity is detected by recognizing the segment after a home root (home on Unix, Users on Windows). The tilde prefix (~) encodes identity implicitly.

Also Available

License

Licensed under either of Apache License, Version 2.0 or MIT License at your option.