{"_id":"@4n6h4x0r/shrinkpath","name":"@4n6h4x0r/shrinkpath","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@4n6h4x0r/shrinkpath","version":"0.1.0","description":"Smart cross-platform path shortening for CLIs, prompts, and tools","type":"module","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./fs":{"types":"./dist/fs-aware.d.ts","import":"./dist/fs-aware.js","require":"./dist/fs-aware.cjs"}},"main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","author":{"name":"4n6h4x0r"},"homepage":"https://github.com/SecurityRonin/shrinkpath/tree/main/ts#readme","bugs":{"url":"https://github.com/SecurityRonin/shrinkpath/issues"},"sideEffects":false,"scripts":{"build":"tsup","test":"vitest run","test:watch":"vitest","lint":"tsc --noEmit","prepublishOnly":"npm run lint && npm run test && npm run build"},"keywords":["path","shorten","shrink","truncate","prompt","fish","cli"],"license":"MIT OR Apache-2.0","repository":{"type":"git","url":"git+https://github.com/SecurityRonin/shrinkpath.git","directory":"ts"},"engines":{"node":">=18"},"devDependencies":{"@types/node":"^25.5.0","@vitest/coverage-v8":"^3.2.4","tsup":"^8","typescript":"^5","vitest":"^3"},"_id":"@4n6h4x0r/shrinkpath@0.1.0","gitHead":"4c634331550ddacf1eda8e36a8d37d46a646e65d","_nodeVersion":"24.4.1","_npmVersion":"11.4.2","dist":{"integrity":"sha512-bS/eJsMkz0I7rVXd1UQyEJsucBN6eTN04wcvh9M9TVmryWtO+VCloQY7+aGmKtXfdW9WfY+nc0ev5CFgY//wKQ==","shasum":"f48fe69a2b29b08dffb2c98616236f1abff7a0b1","tarball":"https://registry.npmjs.org/@4n6h4x0r/shrinkpath/-/shrinkpath-0.1.0.tgz","fileCount":12,"unpackedSize":62577,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIG+GWi8Dp4RckCPftxERN3e6h1DrFosIOIzAMoXF1gzeAiBZNee/GIElsVayFZ3cRsqp2LOHJWt6PTZctG7dFohCWA=="}]},"_npmUser":{"name":"4n6h4x0r","email":"albert@securityronin.com"},"directories":{},"maintainers":[{"name":"4n6h4x0r","email":"albert@securityronin.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/shrinkpath_0.1.0_1773880332617_0.5499117162149614"},"_hasShrinkwrap":false}},"time":{"created":"2026-03-19T00:32:12.498Z","0.1.0":"2026-03-19T00:32:12.772Z","modified":"2026-03-19T00:32:12.941Z"},"maintainers":[{"name":"4n6h4x0r","email":"albert@securityronin.com"}],"description":"Smart cross-platform path shortening for CLIs, prompts, and tools","homepage":"https://github.com/SecurityRonin/shrinkpath/tree/main/ts#readme","keywords":["path","shorten","shrink","truncate","prompt","fish","cli"],"repository":{"type":"git","url":"git+https://github.com/SecurityRonin/shrinkpath.git","directory":"ts"},"author":{"name":"4n6h4x0r"},"bugs":{"url":"https://github.com/SecurityRonin/shrinkpath/issues"},"license":"MIT OR Apache-2.0","readme":"# shrinkpath\n\n**Smart cross-platform path shortening for JavaScript and TypeScript.**\n\n[![npm](https://img.shields.io/npm/v/@4n6h4x0r/shrinkpath.svg)](https://www.npmjs.com/package/@4n6h4x0r/shrinkpath)\n[![CI](https://github.com/SecurityRonin/shrinkpath/actions/workflows/ci.yml/badge.svg)](https://github.com/SecurityRonin/shrinkpath/actions)\n[![License: MIT OR Apache-2.0](https://img.shields.io/npm/l/@4n6h4x0r/shrinkpath.svg)](#license)\n\n```\n/Users/john/Library/Application Support/Code/User/settings.json  (62 chars)\n                            ↓  shrinkTo(path, 35)\n             /Users/john/.../User/settings.json                   (35 chars)\n```\n\nFilename is never truncated. Username is preserved when possible. Everything else\nadapts to fit your target length.\n\n> TypeScript port of the [shrinkpath Rust crate](https://crates.io/crates/shrinkpath). Full feature parity, zero dependencies.\n\n## Why shrinkpath?\n\n- **Target-length guarantee** &mdash; `shrinkTo(path, 30)` always returns &le; 30 chars (unless the filename itself is longer &mdash; filenames are sacred)\n- **Identity-preserving** &mdash; the username/profile segment is the last thing to go, not the first\n- **Cross-platform** &mdash; handles `/home/...`, `~/...`, `C:\\Users\\...`, `\\\\server\\share\\...`, `.\\...` from any host OS\n- **Zero dependencies** &mdash; pure TypeScript, nothing to install beyond this package\n- **No filesystem access** &mdash; works on path strings alone (opt-in `@4n6h4x0r/shrinkpath/fs` subpath for filesystem-aware features)\n- **Dual ESM/CJS** &mdash; works in Node.js, Bun, bundlers, and anywhere JavaScript runs\n\n## Install\n\n```sh\nnpm install @4n6h4x0r/shrinkpath\n```\n\n## Quick Start\n\n```typescript\nimport { shrinkTo, shrinkFish } from '@4n6h4x0r/shrinkpath';\n\n// Hybrid strategy: graduated shortening to fit a target length\nconst short = shrinkTo('/home/john/projects/rust/myapp/src/lib.rs', 30);\n// => '/home/john/.../src/lib.rs' (≤ 30 chars, ends with lib.rs)\n\n// Fish strategy: every directory becomes its first character\nconst fish = shrinkFish('/home/john/projects/rust/myapp/src/lib.rs');\n// => '/h/j/p/r/m/s/lib.rs'\n```\n\n## Strategies\n\nshrinkpath ships four strategies. Pick the one that fits your use case, or use\n**Hybrid** (the default) and let the algorithm decide.\n\n### Fish\n\nAbbreviates every directory segment to its first character. Dot-prefixed\ndirectories keep the dot: `.config` &rarr; `.c`.\n\n```\n/home/john/projects/rust/myapp/src/lib.rs  →  /h/j/p/r/m/s/lib.rs\nC:\\Users\\Admin\\AppData\\Local\\Temp\\file.txt →  C:\\U\\A\\A\\L\\T\\file.txt\n~/projects/rust/file.rs                    →  ~/p/r/file.rs\n```\n\nFish produces the shortest possible result. Use it when every character counts\n(prompts, status bars) and the user can infer the full path from context.\n\n**Tuning knobs:**\n\n```typescript\nimport { shrink } from '@4n6h4x0r/shrinkpath';\n\n// Keep 2 chars per segment instead of 1\nshrink('/home/john/projects/rust/myapp/src/lib.rs', {\n  maxLen: 50,\n  strategy: 'fish',\n  dirLength: 2,\n});\n// => '/ho/jo/pr/ru/my/sr/lib.rs'\n\n// Keep the last N directory segments unabbreviated\nshrink('/home/john/projects/rust/myapp/src/lib.rs', {\n  maxLen: 50,\n  strategy: 'fish',\n  fullLengthDirs: 1,\n});\n// => '/h/j/p/r/m/src/lib.rs'\n```\n\n### Ellipsis\n\nReplaces middle segments with `...`, keeping the identity head (username) and\nthe segments nearest the filename.\n\n```\n/home/john/projects/rust/myapp/src/lib.rs  →  /home/john/.../src/lib.rs\nC:\\Users\\Admin\\AppData\\Local\\Temp\\file.txt →  C:\\Users\\Admin\\...\\file.txt\n```\n\nEllipsis is the most readable strategy. Use it when you have moderate space and\nwant humans to immediately understand the path.\n\n### Hybrid (default)\n\nA graduated four-phase approach that produces the best result for any target\nlength:\n\n```\nPhase 1 — fish expendable segments only:    /home/john/p/r/m/src/lib.rs\nPhase 2 — fish context segments too:        /h/john/p/r/m/s/lib.rs\nPhase 3 — collapse abbreviated runs to ...: /h/john/.../s/lib.rs\nPhase 4 — fish identity (last resort):      /h/j/.../s/lib.rs\n```\n\nEach phase stops as soon as the result fits. If nothing fits, it falls back to\n`/.../<filename>`, then the filename alone.\n\n### Unique\n\nDisambiguates segments against each other within the same path. Each segment is\nabbreviated to the shortest prefix that distinguishes it from every other segment.\n\n```\n/home/documents/downloads/file.txt  →  /h/doc/dow/file.txt\n/Users/Admin/AppData/Application/f  →  /U/Ad/AppD/Appl/f\n```\n\nWhen all first characters are unique, Unique behaves like Fish. When segments\nshare prefixes, it uses the minimum characters needed. Identical segments are\nkept in full (they can't be disambiguated).\n\n```typescript\nimport { shrinkUnique } from '@4n6h4x0r/shrinkpath';\n\nshrinkUnique('/home/documents/downloads/file.txt');\n// => '/h/doc/dow/file.txt'\n```\n\n## Features\n\n### Mapped Locations\n\nSubstitute known path prefixes before shortening. Useful for replacing home\ndirectories, project roots, or well-known paths with short aliases.\n\n```typescript\nimport { shrink } from '@4n6h4x0r/shrinkpath';\n\nconst result = shrink('/home/john/projects/rust/lib.rs', {\n  maxLen: 50,\n  mappedLocations: [\n    ['/home/john', '~'],\n    ['/home/john/projects', 'PROJ:'],\n  ],\n});\n// Longest match wins → 'PROJ:/rust/lib.rs'\n```\n\n### Anchor Segments\n\nMark directory names that should never be abbreviated, regardless of strategy.\n\n```typescript\nimport { shrink } from '@4n6h4x0r/shrinkpath';\n\nconst result = shrink('/home/john/projects/rust/myapp/src/lib.rs', {\n  maxLen: 50,\n  strategy: 'fish',\n  anchors: ['src', 'myapp'],\n});\n// 'myapp' and 'src' kept full, everything else abbreviated\n// => '/h/j/p/r/myapp/src/lib.rs'\n```\n\n### Segment Metadata\n\n`shrinkDetailed()` returns per-segment metadata for building colored prompts,\nclickable breadcrumbs, or tooltip UIs.\n\n```typescript\nimport { shrinkDetailed } from '@4n6h4x0r/shrinkpath';\n\nconst result = shrinkDetailed('/home/john/projects/lib.rs', {\n  maxLen: Infinity,\n  strategy: 'fish',\n});\n\nfor (const seg of result.segments) {\n  if (seg.wasAbbreviated) {\n    // render abbreviated segments in dim color\n    process.stdout.write(`\\x1b[2m${seg.shortened}\\x1b[0m/`);\n  } else if (seg.isFilename) {\n    // render filename in bold\n    process.stdout.write(`\\x1b[1m${seg.shortened}\\x1b[0m`);\n  } else {\n    process.stdout.write(`${seg.shortened}/`);\n  }\n  // seg.original always contains the full text for tooltips\n}\n```\n\n### Filesystem-Aware Features\n\nImport from `@4n6h4x0r/shrinkpath/fs` for features that require filesystem access (Node.js only):\n\n```typescript\nimport { findGitRoot, disambiguateSegment } from '@4n6h4x0r/shrinkpath/fs';\n\n// Find the git repository name for a file path\nconst repo = findGitRoot('/home/john/projects/myapp/src/lib.rs');\n// => 'myapp'\n\n// Find shortest unique prefix by checking sibling directories\n// If /home contains \"documents\", \"downloads\", \"desktop\":\nconst short = disambiguateSegment('/home', 'documents');\n// => 'doc'\n```\n\n## API\n\n### Convenience Functions\n\n```typescript\nimport { shrinkTo, shrinkFish, shrinkEllipsis, shrinkUnique } from '@4n6h4x0r/shrinkpath';\n\nshrinkTo(path, 30)          // Hybrid strategy, target length\nshrinkFish(path)            // Fish abbreviation, no length target\nshrinkEllipsis(path, 30)    // Ellipsis strategy, target length\nshrinkUnique(path)          // Unique disambiguation, no length target\n```\n\n### Full Options\n\n```typescript\nimport { shrink } from '@4n6h4x0r/shrinkpath';\nimport type { ShrinkOptions } from '@4n6h4x0r/shrinkpath';\n\nconst result = shrink(path, {\n  maxLen: 30,                                 // target max length\n  strategy: 'ellipsis',                       // 'hybrid' | 'fish' | 'ellipsis' | 'unique'\n  pathStyle: 'windows',                       // 'unix' | 'windows' (auto-detected by default)\n  ellipsis: '..',                             // custom ellipsis marker\n  dirLength: 2,                               // chars per abbreviated segment (fish/hybrid)\n  fullLengthDirs: 1,                          // keep last N dirs unabbreviated (fish)\n  anchors: ['src'],                           // never abbreviate these segments\n  mappedLocations: [['/home/john', '~']],     // prefix substitutions\n});\n```\n\n### Detailed Result\n\n```typescript\nimport { shrinkDetailed } from '@4n6h4x0r/shrinkpath';\nimport type { ShrinkResult, SegmentInfo } from '@4n6h4x0r/shrinkpath';\n\nconst result: ShrinkResult = shrinkDetailed(path, { maxLen: 30 });\n\nresult.shortened      // the shortened path string\nresult.originalLen    // original path length\nresult.shortenedLen   // shortened path length\nresult.wasTruncated   // whether the path was modified\nresult.detectedStyle  // 'unix' | 'windows'\nresult.segments       // SegmentInfo[] with per-segment metadata\n```\n\n### Types\n\n```typescript\nimport type {\n  Strategy,       // 'hybrid' | 'fish' | 'ellipsis' | 'unique'\n  PathStyle,      // 'unix' | 'windows'\n  ShrinkOptions,  // options object\n  ShrinkResult,   // detailed result\n  SegmentInfo,    // per-segment metadata\n} from '@4n6h4x0r/shrinkpath';\n```\n\n## Platform Support\n\n| Path format | Example | Detected as |\n|---|---|---|\n| Unix absolute | `/home/john/file.rs` | Unix |\n| Tilde home | `~/projects/file.rs` | Unix |\n| macOS `/Users` | `/Users/john/Documents/file.txt` | Unix |\n| Windows drive | `C:\\Users\\Admin\\file.txt` | Windows |\n| Windows UNC | `\\\\server\\share\\dept\\file.xlsx` | Windows |\n| Dot-relative | `.\\src\\main.rs` | Windows |\n| Forward-slash drive | `C:/Users/Admin/file.txt` | Windows |\n| Relative (no prefix) | `src/lib.rs` | Unix |\n| Backslash heuristic | `Users\\Admin\\file.txt` | Windows |\n\nDetection is automatic. Use `pathStyle` to override.\n\n## How It Works\n\nEvery input path is parsed into three parts:\n\n```\n  prefix       segments (directories)         filename\n    │          │                                │\n    ▼          ▼                                ▼\n    /    home / john / projects / rust / src /  lib.rs\n   ~~   ~~~~~~~~~~~~~~~~~~~~~~~~~~────────────  ~~~~~~\n```\n\nEach segment is classified by importance:\n\n| Priority | What | Example | Dropped |\n|---|---|---|---|\n| **Sacred** | Filename | `lib.rs` | Never |\n| **Identity** | Username / profile | `john`, `Admin` | Last |\n| **Context** | Home root | `home`, `Users` | Middle |\n| **Expendable** | Everything else | `projects`, `src` | First |\n\nIdentity is detected by recognizing the segment after a home root (`home` on\nUnix, `Users` on Windows). The tilde prefix (`~`) encodes identity implicitly.\n\n## Also Available\n\n- **Rust crate:** [`shrinkpath` on crates.io](https://crates.io/crates/shrinkpath) (the original implementation)\n\n## License\n\nLicensed under either of [Apache License, Version 2.0](LICENSE-APACHE) or\n[MIT License](LICENSE-MIT) at your option.\n","readmeFilename":"README.md","_rev":"1-e2a9db6a1aeb8695c00fc396463fb19a"}