You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
> Make the file paths your CLI prints ctrl/cmd-clickable
9
9
10
-
## Usage
10
+
CLIs print paths, but making them open can be tricky.
11
+
12
+
Thankfully, [OSC 8 hyperlinks](https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda) exist. They point at an invisible absolute `file://` URL.
11
13
12
-
Install package:
14
+
`clickable-path` is a zero-dependency package to print paths as OSC 8 hyperlinks if your terminal supports them, falling back to a plain label if not.
15
+
16
+
## Usage
13
17
14
18
```sh
15
-
#npm
16
-
npm install package-name
19
+
npm install clickable-path
20
+
```
17
21
18
-
# pnpm
19
-
pnpm install package-name
22
+
> [!NOTE]
23
+
> Requires Node 22.1 or newer, which is where `pathToFileURL` gained the `windows` option used to convert Windows-shaped paths on any platform.
Wraps `path` in an OSC 8 hyperlink pointing at it on disk, labelled with the `cwd`-relative form (plus `:<line>:<column>` if given) or with whatever `options.formatter` returns. Relative input is resolved against `options.cwd`.
40
+
41
+
#### `createLinker(defaults?)`
42
+
43
+
Returns a `{ link }` with `defaults` pre-applied, so you can configure a `formatter` and cwd once rather than at every call site:
The formatter receives the resolved absolute path, plus `line` and `column` if they were passed, and its return value is used without modification. (The default formatter is `path.relative` with a `:<line>:<column>` suffix.) Showing the position is up to the formatter; a label that omits it still links to it, so the file opens at the right line either way. Every `LinkOptions` key can be defaulted this way and overridden per call.
55
+
56
+
> [!TIP]
57
+
> Pad and align inside the formatter, not on the result. The returned string contains invisible escapes, so `link(path).padEnd(20)` pads to the wrong width, while `formatter: absolute => basename(absolute).padEnd(20)` lines up as expected.
58
+
59
+
#### `supportsHyperlinks(stream?)`
60
+
61
+
Whether `stream` (default `process.stdout`) will render hyperlinks.
62
+
63
+
The environment is read on every call rather than snapshotted at module load, so a `.env` file or a `--no-color` flag applied after import is still respected. It costs around a microsecond; if you are linking in a hot loop, call this once and pass the result as `enabled`.
64
+
65
+
#### `LinkOptions`
66
+
67
+
| Option | Default ||
68
+
| --- | --- | --- |
69
+
|`formatter`|`cwd`-relative path plus `:<line>:<column>`| builds the label, given the absolute path, `line` and `column`|
70
+
|`cwd`|`process.cwd()`, read at call time | base for relative paths |
71
+
|`stream`|`process.stdout`| stream whose TTY state gates output |
72
+
|`line` / `column`|| shown as `:<line>:<column>` and linked as `#L<line>,<column>`|
73
+
|`id`|| OSC 8 `id` param, so a label wrapped across lines hovers as one link |
74
+
|`enabled`| detection result | force on/off |
75
+
76
+
Paths are converted with `pathToFileURL`, so spaces, `#`, `?` and non-ASCII characters are percent-encoded. Windows-shaped inputs (`C:\...`, `\\server\share\...`) are converted on any platform.
77
+
78
+
### Non-TTY and CI
79
+
80
+
> [!NOTE]
81
+
> No escapes are emitted when the target stream isn't a TTY, or when `CI` is set, as they would otherwise end up in log files. Netlify is the exception, since it renders build logs as HTML and never allocates a TTY.
82
+
83
+
Overrides, in order of precedence: `FORCE_HYPERLINK` (set to `0` to disable), `--no-hyperlink` / `--hyperlink` flags, then `NO_COLOR` / `NO_HYPERLINK` / `NO_HYPERLINKS`.
84
+
85
+
> [!WARNING]
86
+
> `FORCE_HYPERLINK=1` bypasses every check, including the CI and non-TTY ones. Escape sequences will end up in whatever you are redirecting to.
87
+
88
+
`NO_COLOR` disables hyperlinks here. Colour support and hyperlink support are not the same capability, so there is no general colour-support check, but someone who has asked for no escape sequences at all should get none.
89
+
90
+
### Terminal support
91
+
92
+
| Terminal | Detection |
93
+
| --- | --- |
94
+
| Windows Terminal >= 1.4 |`WT_SESSION` (any other terminal on win32 is treated as unsupported) |
95
+
| VS Code >= 1.72 |`TERM_PROGRAM=vscode` + version |
96
+
| Cursor |`TERM_PROGRAM=vscode` + `CURSOR_TRACE_ID` (own 0.x version line) |
97
+
| iTerm2 >= 3.1 |`TERM_PROGRAM=iTerm.app` + version |
98
+
| WezTerm >= 20200620 |`TERM_PROGRAM=WezTerm`, including Nix's `0-unstable-YYYY-MM-DD` scheme |
99
+
| ghostty |`TERM_PROGRAM=ghostty` or `TERM=xterm-ghostty`|
100
+
| kitty |`TERM=xterm-kitty`|
101
+
| Alacritty >= 0.11 |`TERM=alacritty`|
102
+
| zed, rio, Tabby, Warp, Orca |`TERM_PROGRAM`|
103
+
| GNOME Terminal / VTE >= 0.50.1 |`VTE_VERSION` (0.50.0 is excluded: it segfaults on hyperlinks) |
104
+
| tmux >= 3.4 |`TERM_PROGRAM=tmux` + version |
105
+
106
+
Terminal.app is explicitly unsupported: it ignores OSC 8, though it degrades gracefully to the plain label. TeamCity is excluded. Anything unrecognised gets no escapes, since a wrong positive prints visible junk.
107
+
108
+
Passing `line` (and optionally `column`) appends `:12:3` to the default label and `#L12,3` to the URL. Most terminals ignore the fragment and simply open the file, so treat jump-to-line as a hint. You can decide whether to show a position in a custom `formatter` if you want.
109
+
110
+
> [!IMPORTANT]
111
+
> The comma in `#L12,3` is deliberate. VS Code parses the fragment with `/^L?(\d+)(?:,(\d+))?/`, so `#L12:3` matches the line and silently drops the column.
112
+
113
+
### Differences from `terminal-link` and `supports-hyperlinks`
114
+
115
+
-**When hyperlinks are unsupported, only the label is printed.**`terminal-link` falls back to appending the raw URL (`nuxt.config.ts file:///home/me/nuxt.config.ts`), which is noise in a log file and breaks any width maths. Here the output is exactly what you would have printed anyway.
116
+
-**tmux is detected.** tmux overwrites `TERM_PROGRAM` with `tmux`, hiding the outer terminal, so `supports-hyperlinks` reports no support inside every tmux session. tmux itself has handled OSC 8 since 3.4, so that version and up are supported directly.
117
+
-**Detection reads the environment per call.**`supports-hyperlinks` snapshots `supportsHyperlinks.stdout` at module load, so anything that mutates the environment after import is missed, such as loading a `.env` file, or normalising `--no-color` into `NO_COLOR` while parsing argv.
118
+
-**No colour-support coupling.**`supports-hyperlinks` returns false whenever `supports-color` does, but this isn't necessarily correct. (We still honour `NO_COLOR` if set.)
119
+
-**The OSC 8 `id` param is exposed**, which `terminal-link` does not surface.
120
+
-**Labels are derived, not passed in.**`terminal-link(text, url)` makes every call site build both halves; here the path is the argument and the label comes from a formatter you configure once.
121
+
-**Zero dependencies**, versus `supports-color` + `has-flag` + `ansi-escapes`.
122
+
123
+
## Credits
124
+
125
+
-[`supports-hyperlinks`](https://github.com/chalk/supports-hyperlinks) and [`terminal-link`](https://github.com/sindresorhus/terminal-link) by [Sindre Sorhus](https://github.com/sindresorhus) and [James Talmage](https://github.com/jamestalmage) are excellent. Detection coverage in this package is informed by `supports-hyperlinks`, reimplemented so there are no dependencies and no module-load-time caching. If you want hyperlinks in general rather than paths specifically, `terminal-link` would be a good choice.
126
+
-[Egmont Koblinger](https://github.com/egmontkob)'s [hyperlinks in terminal emulators](https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda) is the de facto OSC 8 spec.
127
+
26
128
## 💻 Development
27
129
28
130
- Clone this repository
@@ -38,11 +140,11 @@ Published under [MIT License](./LICENCE).
0 commit comments