whi 0.2.0

Magically simple PATH management - improved which with PATH manipulation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
# whi

**Stupid simple PATH management**

`whi` is a powerful `which` replacement with PATH manipulation. Find executables, see all matches, and reorder your PATH with simple shell commands.

## Key Feature: Session-Based with Optional Persistence

**By default, `whi` only modifies your current shell session.** Changes are temporary and safe to experiment with. When you're happy with your PATH, use `whi diff` to review changes and `whi save` to persist them.

```bash
# 1. Manipulate PATH in current session (temporary)
$ whid 5 16 7    # Delete entries at indices 5, 16, and 7
$ whic           # Clean duplicate entries
$ whim 10 1      # Move entry at index 10 to position 1

# 2. Review changes before saving
$ whi diff
+ /usr/local/bin/new-tool
- /old/removed/path
↕ /Users/user/.rustup/toolchains/stable-aarch64-apple-darwin/bin

# 3. Persist changes across new terminal sessions
$ whi save
Saved PATH to zsh (68 entries)

# Open new terminal → your changes are still there!
```

**This workflow prevents accidental PATH corruption** and lets you experiment freely.

## Features

- **Session-based by default**: Changes only affect current shell, safe to experiment
- **Persistence when you want it**: Use `whi diff` to review, `whi save` to persist
- **Better than which**: Shows winner by default, all matches with `-a`
- **PATH manipulation**: Move, swap, delete, and clean duplicates with simple commands
- **Winner indication**: Clearly marks which executable would actually run with color
- **PATH indices** (`-i`): Shows the PATH index for each match
- **Full PATH listing** (`-f`): Displays complete PATH with indices
- **Follow symlinks** (`-l`): Resolves and shows canonical targets
- **File metadata** (`-s`): Shows inode, device, size, and modification time
- **Combinable flags**: Unix-style flag combining (e.g., `-ais`, `-ifl`)
- **Pipe-friendly**: Quiet by default, all output to stdout
- **Zero dependencies**: Only libc for isatty(3)

## Installation

From crates.io:

```bash
cargo install whi
```

Or build from source:

```bash
git clone https://github.com/alexykn/whi
cd whi
cargo build --release
```

## Quick Start

### Shell Integration (Recommended)

`whi` provides shell integration that gives you commands to manipulate your PATH directly in your current shell session. Without shell integration, `whi --move` and similar commands would only output a new PATH string - they can't modify your actual shell's PATH variable.

To enable shell integration, add this to your shell config:

**Bash** (`~/.bashrc`):
```bash
eval "$(whi init bash)"
```

**Zsh** (`~/.zshrc`):
```bash
eval "$(whi init zsh)"
```

**Fish** (`~/.config/fish/config.fish`):
```fish
whi init fish | source
```

The `whi init <shell>` command outputs shell-specific functions that you can evaluate/source. This provides seven powerful commands:

- **`whim FROM TO`** - Move PATH entry from index FROM to index TO
  ```bash
  $ whim 10 1      # Move entry at index 10 to position 1
  ```

- **`whis IDX1 IDX2`** - Swap two PATH entries
  ```bash
  $ whis 10 41     # Swap entries at indices 10 and 41
  ```

- **`whip NAME INDEX`** - Make executable at INDEX win (prefer it over others)
  ```bash
  $ whip cargo 50  # Make cargo at index 50 the winner
  ```

- **`whic`** - Clean duplicate PATH entries (keeps first occurrence)
  ```bash
  $ whic           # Remove all duplicate entries
  ```

- **`whid INDEX...`** - Delete PATH entries at one or more indices
  ```bash
  $ whid 5         # Delete entry at index 5
  $ whid 5 16 7    # Delete entries at indices 5, 16, and 7
  ```

- **`whia NAME`** - Show all matches with indices (shortcut for `whi -ia`)
  ```bash
  $ whia cargo     # Equivalent to: whi -ia cargo
  ```

- **`whii [NAME]`** - Show PATH entries or matches with indices (shortcut for `whi -i`)
  ```bash
  $ whii           # Show all PATH entries with indices
  $ whii cargo     # Show cargo matches with indices
  ```

**Important:** These commands only modify your **current shell session**. Changes are temporary until you use `whi save` to persist them.

### Basic Usage

View all PATH entries (with or without indices):

```bash
$ whi
/Users/user/.rustup/toolchains/stable-aarch64-apple-darwin/bin
/opt/homebrew/bin
/usr/local/bin
/usr/bin
...

$ whi -i    # Or use the whii shortcut
[1] /Users/user/.rustup/toolchains/stable-aarch64-apple-darwin/bin
[2] /opt/homebrew/bin
[3] /usr/local/bin
[4] /usr/bin
...
```

Find an executable (like `which`) - shows only the winner:

```bash
$ whi cargo
/Users/user/.rustup/toolchains/stable-aarch64-apple-darwin/bin/cargo
```

See all matches with `-a`:

```bash
$ whi -a cargo
/Users/user/.rustup/toolchains/stable-aarch64-apple-darwin/bin/cargo
/opt/homebrew/bin/cargo
/Users/user/.cargo/bin/cargo
```

With PATH indices using `-i` (or use `whia cargo` shortcut):

```bash
$ whi -ai cargo
[1] /Users/user/.rustup/toolchains/stable-aarch64-apple-darwin/bin/cargo
[2] /opt/homebrew/bin/cargo
[5] /Users/user/.cargo/bin/cargo
```

Follow symlinks with `-l` to see what they point to:

```bash
$ whi -ail cargo
[1] /Users/user/.rustup/toolchains/stable-aarch64-apple-darwin/bin/cargo
[2] /opt/homebrew/bin/cargo → /opt/homebrew/Cellar/rustup/1.28.2/bin/rustup-init
[5] /Users/user/.cargo/bin/cargo → /Users/user/.cargo/bin/rustup
```

Show detailed metadata with `-s`:

```bash
$ whi -as cargo
/Users/user/.rustup/toolchains/stable-aarch64-apple-darwin/bin/cargo
  inode: 152583153, device: 16777233, size: 30854216 bytes
  created:  2025-09-27 10:30:17
  modified: 2025-09-27 10:30:17
/opt/homebrew/bin/cargo
  inode: 103789726, device: 16777233, size: 11154288 bytes
  created:  2025-04-28 15:56:34
  modified: 2025-04-28 15:56:34
/Users/user/.cargo/bin/cargo
  inode: 117539552, device: 16777233, size: 11174016 bytes
  created:  2025-09-03 07:12:56
  modified: 2025-09-03 07:12:56
```

Combine flags for all matches with indices and symlinks:

```bash
$ whi -ail python
[3] /usr/local/bin/python → /usr/local/Cellar/python@3.11/3.11.5/bin/python3.11
[8] /usr/bin/python
[12] /opt/homebrew/bin/python → /opt/homebrew/Cellar/python@3.12/3.12.0/bin/python3.12
```

### PATH Manipulation Examples

See which cargo is winning and make a different one win:

```bash
$ whia cargo
[1] /Users/user/.rustup/toolchains/stable-aarch64-apple-darwin/bin/cargo
[2] /opt/homebrew/bin/cargo
[5] /Users/user/.cargo/bin/cargo

$ whip cargo 5    # Make cargo at index 5 the winner
$ whia cargo
[1] /Users/user/.cargo/bin/cargo
[2] /Users/user/.rustup/toolchains/stable-aarch64-apple-darwin/bin/cargo
[3] /opt/homebrew/bin/cargo
```

Move PATH entries to reorder them:

```bash
$ whim 10 1      # Move entry at index 10 to position 1
$ whim 50 3      # Move entry at index 50 to position 3
```

Swap two PATH entries:

```bash
$ whis 10 41     # Swap entries at indices 10 and 41
```

Clean duplicate entries:

```bash
$ whia cargo
[4] /Users/user/.cargo/bin/cargo
[6] /opt/homebrew/bin/cargo
[54] /Users/user/.cargo/bin/cargo    # Duplicate!

$ whic           # Remove duplicates
$ whia cargo
[4] /Users/user/.cargo/bin/cargo
[6] /opt/homebrew/bin/cargo
```

Delete specific entries:

```bash
$ whid 6 54      # Delete entries at indices 6 and 54
$ whid 5 16 7    # Delete multiple entries at once
```

### Making Changes Persistent

**All the commands above only affect your current shell session.** To make changes permanent:

```bash
# 1. Make changes in your current session
$ whic                           # Clean duplicates
$ whid 10 20                     # Delete unwanted entries
$ whim 5 1                       # Reorder as needed

# 2. Review what changed
$ whi diff
- /Users/user/.rye/shims
- /Users/user/.cargo/bin
↕ /Users/user/.rustup/toolchains/stable-aarch64-apple-darwin/bin

# 3. Save changes (persists across new terminal sessions)
$ whi save
Saved PATH to zsh (65 entries)

# Or save to all shells at once
$ whi save all
Saved PATH to bash (65 entries)
Saved PATH to zsh (65 entries)
Saved PATH to fish (65 entries)
```

After running `whi save`, your changes are automatically loaded in new terminal sessions. The saved PATH is stored in `~/.whi/saved_path_<shell>` and loaded by a single line added to your shell config file.

**You can experiment safely** because changes are session-only until you explicitly save them.

### Understanding `whi diff`

The `whi diff` command shows what changed between your current session and the saved PATH. It uses intelligent markers to distinguish different types of changes:

**Basic diff** (`whi diff`) - Shows only explicit changes:
```bash
$ whi diff
+ /new/path/added                    # You added this path
- /old/path/removed                  # Deleted with whid
- /Users/user/.cargo/bin             # Duplicate removed by whic
↕ /Users/user/.rustup/.../bin        # You moved this with whim/whis/whip
```

**Full diff** (`whi diff full`) - Shows everything including implicit shifts:
```bash
$ whi diff full
+ /new/path/added                    # Explicitly added
- /old/path/removed                  # Explicitly deleted
↕ /Users/user/.rustup/.../bin        # Explicitly moved by you
M /opt/homebrew/bin                  # Implicitly shifted (side effect)
M /usr/local/bin                     # Implicitly shifted (side effect)
U /usr/bin                           # Unchanged position
U /bin                               # Unchanged position
```

**Diff markers explained:**
- **`+`** (green) - New path added to your PATH
- **`-`** (red) - Path removed (via `whid` or duplicate removed by `whic`)
- **``** (cyan) - Path explicitly moved by you using `whim`, `whis`, or `whip`
- **`M`** - Path implicitly moved (shifted as a side effect of your operations) - *full mode only*
- **`U`** - Path unchanged (same position as saved) - *full mode only*

**Session tracking:** `whi` tracks all operations in your current shell session (`whim`, `whis`, `whip`, `whic`, `whid`) to accurately distinguish between changes you explicitly made versus paths that shifted as a side effect. This makes `whi diff` highly accurate in showing what you actually changed.

**Use cases:**
```bash
# Quick check - see only what you explicitly changed
$ whi diff

# Detailed review - see everything including ripple effects
$ whi diff full

# Compare with saved PATH from a specific shell
$ whi diff bash
$ whi diff zsh
$ whi diff fish
```

### Other Usage Examples

Read multiple names from stdin:

```bash
$ echo -e "python\\nnode\\ncargo" | whi
/usr/bin/python
/usr/local/bin/node
/Users/user/.cargo/bin/cargo
```

Check multiple executables at once:

```bash
$ whi python node cargo gcc
/usr/bin/python
/usr/local/bin/node
/Users/user/.cargo/bin/cargo
/usr/bin/gcc
```

Show all PATH entries (with or without indices):

```bash
$ whi          # Plain listing
/Users/user/.rustup/toolchains/stable-aarch64-apple-darwin/bin
/opt/homebrew/bin
/usr/local/bin
/usr/bin
...

$ whi -i       # With indices
[1] /Users/user/.rustup/toolchains/stable-aarch64-apple-darwin/bin
[2] /opt/homebrew/bin
[3] /usr/local/bin
[4] /usr/bin
...
```

Use custom PATH:

```bash
$ whi --path="/usr/local/bin:/usr/bin" python
/usr/local/bin/python
```

## Command-Line Options

### Flags

Short flags can be combined Unix-style (e.g., `-ai` = `-a -i`, `-ais` = `-a -i -s`).

- **`-a, --all`** - Show all PATH matches (default: only winner)
- **`-f, --full`** - Show all matches + full PATH listing (implies `-a`; directories with matches highlighted in color)
- **`-i, --index`** - Show PATH index next to each entry
- **`-l, -L, --follow-symlinks`** - Resolve and show canonical targets
- **`-o, --one`** - Only print the first match per name
- **`-s, --stat`** - Include inode/device/mtime/size metadata
- **`-0, --print0`** - NUL-separated output for use with xargs
- **`-q, --quiet`** - Suppress non-fatal stderr warnings
- **`--silent`** - Print nothing to stderr, use exit codes only
- **`--show-nonexec`** - Also list files that exist but aren't executable
- **`-h, --help`** - Print help information

### PATH Manipulation (Session Only)

These commands output a modified PATH string to stdout. Use shell integration (see above) to actually modify your current shell's PATH. **Changes are temporary** until you use `whi save`.

- **`--move <FROM> <TO>`** - Move PATH entry from index FROM to index TO
  ```bash
  $ whi --move 10 1
  /path/at/10:/path/at/1:/path/at/2:...
  ```

- **`--swap <IDX1> <IDX2>`** - Swap PATH entries at indices IDX1 and IDX2
  ```bash
  $ whi --swap 10 41
  /modified/path/string/...
  ```

- **`--prefer <NAME> <INDEX>`** - Make executable NAME at INDEX win
  ```bash
  $ whi --prefer cargo 50
  /modified/path/string/...
  ```

- **`--clean` / `-c`** - Remove duplicate PATH entries (keeps first occurrence)
  ```bash
  $ whi --clean
  /deduplicated/path/...
  ```

- **`--delete <INDEX>...` / `-d`** - Delete one or more PATH entries by index
  ```bash
  $ whi --delete 5 16 7
  /path/without/those/entries...
  ```

### Persistence

- **`whi save [SHELL]`** - Save current PATH persistently
  ```bash
  $ whi save           # Auto-detect current shell
  $ whi save bash      # Save for bash
  $ whi save zsh       # Save for zsh
  $ whi save fish      # Save for fish
  $ whi save all       # Save for all shells
  ```

- **`whi diff [SHELL|full]`** - Show differences between current and saved PATH
  ```bash
  $ whi diff           # Compare with saved PATH for current shell (explicit changes only)
  $ whi diff full      # Show everything including implicit shifts (M) and unchanged (U)
  $ whi diff zsh       # Compare with saved PATH for zsh
  $ whi diff fish      # Compare with saved PATH for fish
  ```

  **Diff markers:**
  - `+` (green) - Added paths
  - `-` (red) - Removed paths (including duplicates from `whic`)
  - `` (cyan) - Explicitly moved paths (`whim`/`whis`/`whip`)
  - `M` - Implicitly shifted paths (*full mode only*)
  - `U` - Unchanged paths (*full mode only*)

After `whi save`, changes persist across new terminal sessions. Use `whi diff` to review changes before saving.

### Other Options

- **`--path <PATH>`** - Override environment PATH string
  ```bash
  $ whi --path="/usr/local/bin:/usr/bin" python
  ```

- **`--color <WHEN>`** - Colorize output: `auto`, `never`, `always` [default: auto]
  ```bash
  $ whi --color=always cargo
  ```

### Shell Integration Command

- **`whi init <SHELL>`** - Output shell integration code for bash, zsh, or fish
  ```bash
  $ whi init bash    # Output bash functions
  $ whi init zsh     # Output zsh functions
  $ whi init fish    # Output fish functions
  ```

## Exit Codes

- `0` - All names found
- `1` - At least one not found
- `2` - Usage error
- `3` - I/O or environment error

## Examples

Find all versions of Python in PATH:

```bash
$ whi -a python python3 python3.11
```

Check which node would run with indices:

```bash
$ whi -i node
```

Use with xargs to check executables:

```bash
$ whi -0 python node cargo | xargs -0 -n1 file
```

Find all versions with metadata:

```bash
$ whi -ais gcc
```

Show all PATH entries with indices:

```bash
$ whi -i       # Or use whii shortcut
[1] /usr/local/bin
[2] /usr/bin
[3] /bin
...
```

## Comparison with `which`

| Feature | `which` | `whi` |
|---------|---------|----------|
| Show first match || ✓ (default) |
| Show all matches | Some versions with `-a` | ✓ With `-a` |
| Show PATH indices || ✓ With `-i` |
| Full PATH listing || ✓ With `-f` |
| Follow symlinks | Some versions | ✓ With `-l/-L` |
| File metadata || ✓ With `-s` |
| PATH manipulation || ✓ With shell integration |
| Clean duplicates || ✓ With `whic` |
| Delete entries || ✓ With `whid` |
| Session-based changes || ✓ Safe to experiment |
| Persistent changes || ✓ With `whi save` |
| Intelligent diff || ✓ With `whi diff` (tracks explicit vs implicit changes) |
| Combinable flags |||
| Multiple names |||
| Stdin input |||
| Pipe-friendly | Varies ||

## Why?

Ever wonder:
- Which version of `python` or `node` is actually running?
- How to make a different version win without editing shell configs?
- What other versions exist on your PATH?
- What's the actual order of your PATH directories?
- How to safely experiment with PATH changes without breaking things?

`whi` answers all these questions and lets you:
- **Experiment safely** with session-only changes
- **Review before committing** with `whi diff`
- **Persist when ready** with `whi save`
- **Manipulate PATH on the fly** without manually editing config files

## License

MIT

## Author

Alexander Knott <alexander.knott@posteo.de>