@codebend3r/che 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +424 -0
- package/bin/che.py +869 -0
- package/bin/commands.py +872 -0
- package/bin/drives/eject-all-drives.py +203 -0
- package/bin/drives/mount-all-drives.py +140 -0
- package/bin/drives/ping-nas.py +242 -0
- package/bin/files/compress-folders.py +188 -0
- package/bin/files/delete-by-ext.py +108 -0
- package/bin/files/delete-empty-folders.py +136 -0
- package/bin/files/delete-smb-files.py +105 -0
- package/bin/files/files-under-size.py +132 -0
- package/bin/files/find-largest-files.py +102 -0
- package/bin/files/make-alpha-dir.py +63 -0
- package/bin/git/all-actions.py +545 -0
- package/bin/git/checkout-my-branches.py +147 -0
- package/bin/git/clean-stale-branches.py +161 -0
- package/bin/git/prune-worktrees.py +143 -0
- package/bin/git/sync-all-branches.py +368 -0
- package/bin/git/update-local-branches.py +195 -0
- package/bin/install.py +1515 -0
- package/bin/shellgen.py +782 -0
- package/bin/system/btop-launch.py +114 -0
- package/bin/system/update-brew.py +146 -0
- package/bin/tui.py +648 -0
- package/bin/utils.py +836 -0
- package/bin/video/delete-duplicate-videos.py +313 -0
- package/bin/video/detect-green-magenta-videos.py +268 -0
- package/bin/video/find-movie-by-year.py +84 -0
- package/bin/video/find-video-mkv-issues.py +242 -0
- package/bin/video/fix-codecs.py +209 -0
- package/bin/video/largest-tv-shows.py +175 -0
- package/bin/video/remove-metadata.py +126 -0
- package/bin/video/rename-video-file.py +329 -0
- package/bin/video/scan-videos-audio-language.py +110 -0
- package/bin/video/show-codecs.py +111 -0
- package/bin/video/validate-video-files.py +114 -0
- package/bin/video/video-list.py +120 -0
- package/package.json +54 -0
- package/shell/che.bash +382 -0
- package/shell/che.fish +282 -0
- package/shell/che.zsh +389 -0
- package/shell/completions/_che +146 -0
- package/shell/completions/che.bash +107 -0
- package/shell/completions/che.fish +371 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 CJ Rivas
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# `che`
|
|
4
|
+
|
|
5
|
+
**A collection of helper scripts shared amongst computers, called by name from
|
|
6
|
+
your shell, and browsable through one menu.**
|
|
7
|
+
|
|
8
|
+
[](https://github.com/codebend3r/che/actions/workflows/ci.yml)
|
|
9
|
+
[](https://www.npmjs.com/package/@codebend3r/che)
|
|
10
|
+

|
|
11
|
+

|
|
12
|
+

|
|
13
|
+
|
|
14
|
+
<img src="menu.png" alt="The che menu: commands grouped by category on the left, details for the selected command on the right" width="850">
|
|
15
|
+
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
The scripts are Python, standard library only. The names you type
|
|
19
|
+
(`delete-by-ext`, `sync-all-branches`, `show-codecs`, …) are shell functions
|
|
20
|
+
generated from one manifest, `bin/commands.py`, and installed into whichever
|
|
21
|
+
shells you use.
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
che browse everything: type to filter, Enter to run
|
|
25
|
+
che <command> [args…] run one directly
|
|
26
|
+
delete-by-ext --path=… or just call it by name
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
<!-- che:count -->
|
|
30
|
+
33 commands across 5 categories, plus 6 built into `che` itself
|
|
31
|
+
<!-- /che:count -->
|
|
32
|
+
|
|
33
|
+
## Highlights
|
|
34
|
+
|
|
35
|
+
- **One manifest, everything generated.** Wrappers, completions, the menu, the
|
|
36
|
+
doctor report and the tables in this README all come from `bin/commands.py`;
|
|
37
|
+
`bun run ci` fails if any of them drift.
|
|
38
|
+
- **Destructive commands preview by default.** Every deleter has a `-dr` twin
|
|
39
|
+
that only prints, and the menu opens on the dry-run.
|
|
40
|
+
- **Standard library only.** A bare `python3` 3.12+ runs everything; no venv,
|
|
41
|
+
no `pip install`, on any machine the repo is cloned to.
|
|
42
|
+
- **Your dotfiles stay yours.** The installer writes a few lines between
|
|
43
|
+
markers, backs every rc file up first, and never touches anything outside
|
|
44
|
+
the markers.
|
|
45
|
+
- **`che doctor` knows the whole story.** Interpreter, generated files, rc
|
|
46
|
+
blocks, external tools, alias collisions — one command, non-zero on failure.
|
|
47
|
+
|
|
48
|
+
## Install
|
|
49
|
+
|
|
50
|
+
From a clone:
|
|
51
|
+
|
|
52
|
+
```sh
|
|
53
|
+
git clone https://github.com/codebend3r/che.git ~/Developer/git/che
|
|
54
|
+
~/Developer/git/che/install.sh
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Or in one line, which clones for you (into `~/Developer/git/shell-functions`,
|
|
58
|
+
or wherever `CHE_HOME` points):
|
|
59
|
+
|
|
60
|
+
```sh
|
|
61
|
+
curl -fsSL https://raw.githubusercontent.com/codebend3r/che/main/install.sh | bash
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Or from npm, if Node is around:
|
|
65
|
+
|
|
66
|
+
```sh
|
|
67
|
+
npm install -g @codebend3r/che
|
|
68
|
+
che # offers to install the wrappers on first run
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The wrappers point back at wherever the package landed, so install it for real
|
|
72
|
+
rather than through `npx`, whose cache is temporary. Update that kind of install
|
|
73
|
+
with `npm install -g @codebend3r/che@latest`; `che update` only pulls a git
|
|
74
|
+
clone, and says so.
|
|
75
|
+
|
|
76
|
+
`install.sh` finds a suitable python3, then hands over to `bin/install.py`,
|
|
77
|
+
which opens a first-run wizard: it lists the shells it found and the rc files
|
|
78
|
+
it would touch, asks whether to add a `che` on `PATH` and whether to keep the
|
|
79
|
+
completion chimes, offers to remove hand-written wrappers left over from before
|
|
80
|
+
the installer existed, and reports any missing external tool (offering to
|
|
81
|
+
`brew install` them when Homebrew is present).
|
|
82
|
+
|
|
83
|
+
<div align="center">
|
|
84
|
+
<img src="first-run.png" alt="The first run screen: che is not installed in this shell yet, offering to install, open the menu, or quit" width="700">
|
|
85
|
+
<br>
|
|
86
|
+
<em>Running <code>che</code> before installing offers to set itself up.</em>
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
Skip the wizard:
|
|
90
|
+
|
|
91
|
+
```sh
|
|
92
|
+
./install.sh --yes # into the shells it detects
|
|
93
|
+
./install.sh --yes --shells=zsh,fish # or the ones you name
|
|
94
|
+
./install.sh --dry-run # report what would change, write nothing
|
|
95
|
+
./install.sh --yes --replace-legacy # also remove pre-che hand-written wrappers
|
|
96
|
+
./install.sh --print # print the rc block, install nothing
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Every rc file is copied to `~/.config/che/backups/` before it is edited.
|
|
100
|
+
|
|
101
|
+
### What lands where
|
|
102
|
+
|
|
103
|
+
| Thing | Where |
|
|
104
|
+
| --- | --- |
|
|
105
|
+
| The rc block, six or seven lines between markers | `~/.zshrc` (or `$ZDOTDIR/.zshrc`), `~/.bashrc` and `~/.bash_profile` (whichever exist), `~/.config/fish/config.fish`, and `~/.profile` only if you ask for `sh` |
|
|
106
|
+
| The wrappers themselves | `shell/che.zsh`, `shell/che.bash`, `shell/che.fish` in this repo |
|
|
107
|
+
| Tab completions | `shell/completions/`, sourced by the same wrapper file |
|
|
108
|
+
| A `che` executable, for cron and scripts that never read an rc file | `~/.local/bin/che` |
|
|
109
|
+
| What you answered | `~/.config/che/config.json` |
|
|
110
|
+
|
|
111
|
+
The block itself is small on purpose, so upgrading che never rewrites your
|
|
112
|
+
dotfiles:
|
|
113
|
+
|
|
114
|
+
```zsh
|
|
115
|
+
# >>> che shell functions >>>
|
|
116
|
+
# Managed by `che install` (che 1.0.0). Edits here are overwritten.
|
|
117
|
+
export CHE_HOME=/Users/you/Developer/git/shell-functions
|
|
118
|
+
export CHE_PYTHON=/opt/homebrew/bin/python3
|
|
119
|
+
[ -r /Users/you/Developer/git/shell-functions/shell/che.zsh ] && . /Users/you/Developer/git/shell-functions/shell/che.zsh
|
|
120
|
+
case ":$PATH:" in *":/Users/you/.local/bin:"*) ;; *) PATH="/Users/you/.local/bin:$PATH" ;; esac
|
|
121
|
+
# <<< che shell functions <<<
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Everything between those markers belongs to `che install` and is rewritten on
|
|
125
|
+
every install. Everything outside them is yours and is never touched.
|
|
126
|
+
|
|
127
|
+
Then reload:
|
|
128
|
+
|
|
129
|
+
```sh
|
|
130
|
+
exec zsh # or exec bash / exec fish
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**Requires Python 3.12+** and nothing else to run the scripts. The installer
|
|
134
|
+
picks the newest suitable interpreter it can find and records its path in the
|
|
135
|
+
block, so an old `/usr/bin/python3` does not matter as long as some newer
|
|
136
|
+
python3 exists. `brew install python@3.13` if none does.
|
|
137
|
+
|
|
138
|
+
### Uninstall
|
|
139
|
+
|
|
140
|
+
```sh
|
|
141
|
+
che uninstall # remove the block, the shim and the completions
|
|
142
|
+
che uninstall --purge # also delete ~/.config/che
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
The repo itself is left alone; delete the directory to finish.
|
|
146
|
+
|
|
147
|
+
## Using it
|
|
148
|
+
|
|
149
|
+
`che` on its own opens the menu. Anything that can preview **previews by
|
|
150
|
+
default**, so pressing Enter on `delete-by-ext` shows you what it would delete
|
|
151
|
+
rather than deleting it.
|
|
152
|
+
|
|
153
|
+
Type to filter — the list narrows as you go, and the right pane always shows
|
|
154
|
+
the selected command's summary, script, required tools and flags:
|
|
155
|
+
|
|
156
|
+
<div align="center">
|
|
157
|
+
<img src="search.png" alt="The menu filtered by the search term branch, showing the five matching commands" width="850">
|
|
158
|
+
</div>
|
|
159
|
+
|
|
160
|
+
<!-- che:keys -->
|
|
161
|
+
| Key | Does |
|
|
162
|
+
| --- | --- |
|
|
163
|
+
| `↑ ↓ / j k` | move |
|
|
164
|
+
| `PgUp PgDn / g G` | jump |
|
|
165
|
+
| `⏎` | run (asks for arguments) |
|
|
166
|
+
| `x` | run with no arguments |
|
|
167
|
+
| `d` | toggle dry-run for this command |
|
|
168
|
+
| `h` | show the command's own --help |
|
|
169
|
+
| `/` | search, Esc to clear |
|
|
170
|
+
| `?` | this list |
|
|
171
|
+
| `q` | quit |
|
|
172
|
+
<!-- /che:keys -->
|
|
173
|
+
|
|
174
|
+
Every command is also a plain shell function, so `type delete-by-ext` shows
|
|
175
|
+
exactly what it runs, and the destructive ones have a `-dr` twin that only
|
|
176
|
+
prints:
|
|
177
|
+
|
|
178
|
+
```sh
|
|
179
|
+
delete-by-ext --path=/Volumes/Media --ext=nfo,txt # deletes
|
|
180
|
+
delete-by-ext-dr --path=/Volumes/Media --ext=nfo # shows what it would delete
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
`che <command>` runs the same thing with the same environment the wrapper would
|
|
184
|
+
have used, and `che help <command>` prints that script's own `--help`.
|
|
185
|
+
|
|
186
|
+
### Commands
|
|
187
|
+
|
|
188
|
+
<!-- che:commands -->
|
|
189
|
+
| Command | Does | Preview |
|
|
190
|
+
| --- | --- | --- |
|
|
191
|
+
| **Git** | *Branch hygiene, worktrees and GitHub Actions* | |
|
|
192
|
+
| `all-actions` | GitHub Actions status for every open PR you authored | |
|
|
193
|
+
| `all-actions-watch` | Same as all-actions, refreshed on an interval | |
|
|
194
|
+
| `checkout-my-branches` | Check out recent remote branches you authored that aren't local yet | |
|
|
195
|
+
| `clean-stale-branches` | Delete local branches whose upstream is gone | `clean-stale-branches-dr` |
|
|
196
|
+
| `prune-worktrees` | Remove every linked worktree, then prune stale admin records | `prune-worktrees-dr` |
|
|
197
|
+
| `sync-all-branches` | Push every worktree, collapse the pushed ones, then tidy branches | `sync-all-branches-dr` |
|
|
198
|
+
| `update-from-origin` | Same as sync-all-branches but never pushes: fetch, collapse, rebase | |
|
|
199
|
+
| `update-local-branches` | Rebase every local branch that has an upstream onto origin | `--dry-run` |
|
|
200
|
+
| **Video** | *Codecs, renaming, dedupe and metadata* | |
|
|
201
|
+
| `show-codecs` | Report media files outside the Direct Play codec/container set | |
|
|
202
|
+
| `fix-codecs` | Re-encode media to h265/aac mp4 so it Direct Plays | `fix-codecs-dr` |
|
|
203
|
+
| `find-video-mkv-issues` | Scan MKV files and estimate Plex direct-play compatibility | |
|
|
204
|
+
| `validate-video-files` | Check .mp4/.mkv files decode by playing one frame with mpv | |
|
|
205
|
+
| `scan-videos-audio-language` | Print the audio language tags of every video under a path | |
|
|
206
|
+
| `remove-metadata` | Strip all metadata from video files recursively | none |
|
|
207
|
+
| `rename-video-file` | Title-case video filenames (and optionally their folders) | `--dry-run` |
|
|
208
|
+
| `delete-duplicate-videos` | Delete duplicate MKV/MP4 files under a root directory | `delete-duplicate-videos-dr` |
|
|
209
|
+
| `video-list` | List .mp4/.mkv files under a path with human-readable sizes | |
|
|
210
|
+
| `detect-green-magenta-videos` | Detect videos with the green/magenta chroma artifact | |
|
|
211
|
+
| `find-movie-by-year` | Find movie folders whose name ends with "(YYYY)" | |
|
|
212
|
+
| `largest-tv-shows` | Rank TV show folders in a library by total size on disk | |
|
|
213
|
+
| **Files** | *Delete, compress and inspect by size or extension* | |
|
|
214
|
+
| `delete-by-ext` | Delete files under a path matching a set of extensions | `delete-by-ext-dr` |
|
|
215
|
+
| `delete-empty-folders` | Delete truly-empty directories under a path, cascading upwards | `delete-empty-folders-dr` |
|
|
216
|
+
| `delete-smb-files` | Delete .smbdelete* files left behind by an SMB share | `delete-smb-files-dr` |
|
|
217
|
+
| `files-under-size` | Find video files at or under a size threshold | `files-under-size-dr` |
|
|
218
|
+
| `find-largest-files` | List the largest files under a path, biggest first | |
|
|
219
|
+
| `make-alpha-dir` | Create '#' and A-Z bucket folders under a parent directory | |
|
|
220
|
+
| `compress-folders` | Zip every immediate subfolder of a path at max compression | `--dry-run` |
|
|
221
|
+
| `list-permission` | Show ownership and mode of a volume under /Volumes | |
|
|
222
|
+
| **Drives** | *Mount, eject and keep-alive for the NAS volumes* | |
|
|
223
|
+
| `mount-all-drives` | Mount all NAS drives over SMB via AppleScript | |
|
|
224
|
+
| `eject-all-drives` | Eject all NAS volumes from /Volumes | `eject-all-drives-dr` |
|
|
225
|
+
| `ping-nas` | Keep-alive pinger that remounts a NAS drive that dropped off | |
|
|
226
|
+
| **System** | *Homebrew, monitoring and the machine itself* | |
|
|
227
|
+
| `update-brew` | Update Homebrew, upgrade formulae and casks, then clean up | `update-brew-dr` |
|
|
228
|
+
| `btop` | Launch btop with a gruvbox theme matching the macOS appearance | |
|
|
229
|
+
<!-- /che:commands -->
|
|
230
|
+
|
|
231
|
+
Two commands have no `-dr` twin. `remove-metadata` cannot preview at all, since
|
|
232
|
+
exiftool rewrites in place. `rename-video-file` does not need one: it already
|
|
233
|
+
defaults to `--dry-run=true`, so the real rename is the one that needs
|
|
234
|
+
`--dry-run=false`.
|
|
235
|
+
|
|
236
|
+
### Managing the install
|
|
237
|
+
|
|
238
|
+
<!-- che:builtins -->
|
|
239
|
+
| Command | Does |
|
|
240
|
+
| --- | --- |
|
|
241
|
+
| `che install` | Install the shell wrappers into your shell startup files |
|
|
242
|
+
| `che update` | Pull the latest scripts and refresh the installed wrappers |
|
|
243
|
+
| `che doctor` | Check the install, the interpreter and every external tool |
|
|
244
|
+
| `che uninstall` | Remove the wrappers, the shim and the completions |
|
|
245
|
+
| `che list` | List every command, one per line |
|
|
246
|
+
| `che completions` | Print the completion script for a shell |
|
|
247
|
+
<!-- /che:builtins -->
|
|
248
|
+
|
|
249
|
+
`che doctor` checks the interpreter, the repo, whether the generated files are
|
|
250
|
+
current, whether each rc file has the block, whether `che` is on `PATH`, every
|
|
251
|
+
external tool, and anything shadowing a wrapper. It exits non-zero on a real
|
|
252
|
+
failure, so it works in a health check. `che doctor --json` for a machine.
|
|
253
|
+
|
|
254
|
+
<div align="center">
|
|
255
|
+
<img src="doctor.png" alt="che doctor output: green checks for python, repo and generated files, with problems and warnings each followed by the command that fixes them" width="850">
|
|
256
|
+
<br>
|
|
257
|
+
<em>Every problem <code>che doctor</code> reports comes with the command that fixes it.</em>
|
|
258
|
+
</div>
|
|
259
|
+
|
|
260
|
+
### Environment
|
|
261
|
+
|
|
262
|
+
| Variable | Effect |
|
|
263
|
+
| --- | --- |
|
|
264
|
+
| `CHE_HOME` | Where this repo lives. Set by the block; the wrapper file falls back to its own location. |
|
|
265
|
+
| `CHE_PYTHON` | Interpreter the wrappers use. Set by the block to the one the installer verified. |
|
|
266
|
+
| `CHE_SOUNDS` | `0` turns off the `playsound-N` chime after every command. |
|
|
267
|
+
| `DRY_RUN` | What the wrappers set for the destructive commands. Exporting it yourself changes nothing for them, since the wrapper sets it explicitly on every call; it only reaches a script invoked directly. |
|
|
268
|
+
| `NO_COLOR` | Honoured by every script and by the menu. |
|
|
269
|
+
|
|
270
|
+
## How it fits together
|
|
271
|
+
|
|
272
|
+
`bin/commands.py` is the single source of truth. The wrappers, the completions,
|
|
273
|
+
the menu, the `doctor` report and the tables in this README are all generated
|
|
274
|
+
from it, and `bun run ci` fails if any of them drift.
|
|
275
|
+
|
|
276
|
+
### Adding a command
|
|
277
|
+
|
|
278
|
+
1. Write the script under `bin/<category>/` and `chmod +x` it.
|
|
279
|
+
2. Add a `Command(...)` to `bin/commands.py`: name, summary, script, sound,
|
|
280
|
+
whether it can preview, which external tools it needs, its flags, and the
|
|
281
|
+
two or three questions the menu should ask.
|
|
282
|
+
3. `bun run generate` to rewrite `shell/` and this README's tables, and commit
|
|
283
|
+
the result.
|
|
284
|
+
4. `che install` on each machine, or `che update`, which does both.
|
|
285
|
+
|
|
286
|
+
There is no step where you hand-edit a wrapper into `~/.zshrc`, and no step
|
|
287
|
+
where you copy one machine's rc file to another.
|
|
288
|
+
|
|
289
|
+
### Layout
|
|
290
|
+
|
|
291
|
+
```
|
|
292
|
+
bin/
|
|
293
|
+
che.py the dispatcher and the interactive menu
|
|
294
|
+
commands.py the command manifest: the source of truth for everything
|
|
295
|
+
install.py installer, first-run wizard, doctor, self-update
|
|
296
|
+
shellgen.py renders the wrappers, completions and README tables
|
|
297
|
+
tui.py terminal primitives: raw input, frame buffer, prompts
|
|
298
|
+
utils.py shared library: logging, arg parsing, subprocess, files
|
|
299
|
+
drives/ mount, eject, and keep-alive for the NAS volumes
|
|
300
|
+
files/ delete/compress/inspect files by extension or size
|
|
301
|
+
git/ branch hygiene, worktrees, GitHub Actions status
|
|
302
|
+
system/ Homebrew updates, btop launcher
|
|
303
|
+
video/ codec inspection, renaming, dedupe, metadata
|
|
304
|
+
shell/ GENERATED wrappers and completions, do not edit
|
|
305
|
+
test/ pytest suites
|
|
306
|
+
install.sh bootstrap for a machine with nothing on it
|
|
307
|
+
.zshrc a shim that sources shell/che.zsh, kept for old setups
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
## CLI conventions
|
|
311
|
+
|
|
312
|
+
Every script follows the same shape, described in full at the top of
|
|
313
|
+
`bin/utils.py`:
|
|
314
|
+
|
|
315
|
+
- **Long flags only**, `--name=value` for values.
|
|
316
|
+
- **Booleans** accept a bare `--flag` *and* `--flag=true|false` (plus
|
|
317
|
+
`yes|no`, `1|0`, `on|off`). A bare `--flag` never swallows the next argument.
|
|
318
|
+
- **`--help`** on every script.
|
|
319
|
+
- **Destructive scripts default to dry-run.** `delete-by-ext`,
|
|
320
|
+
`delete-empty-folders`, `delete-smb-files`, `files-under-size`, `fix-codecs`,
|
|
321
|
+
`delete-duplicate-videos` and `prune-worktrees` read `DRY_RUN` and preview
|
|
322
|
+
unless told otherwise; the generated wrapper sets `DRY_RUN=false` for the
|
|
323
|
+
real thing and the `-dr` twin sets it back to `true`.
|
|
324
|
+
- **A symlinked `--path` is refused.** `find` never followed a symlinked start
|
|
325
|
+
point, so pointing a delete tool at `~/media -> /Volumes/...` used to be a
|
|
326
|
+
silent no-op. Pass the real path to act on it.
|
|
327
|
+
|
|
328
|
+
## Syncing branches and worktrees
|
|
329
|
+
|
|
330
|
+
```sh
|
|
331
|
+
sync-all-branches # push every worktree, collapse the pushed ones, then sync
|
|
332
|
+
sync-all-branches-dr # preview only
|
|
333
|
+
update-from-origin # same, but --no-push: only collapses already-pushed worktrees
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
A worktree is a temporary workspace. Once its commits are on origin the
|
|
337
|
+
directory holds nothing the remote doesn't, so `sync-all-branches` pushes it,
|
|
338
|
+
removes the directory, and leaves the branch behind as an ordinary local
|
|
339
|
+
branch. That also clears the `already used by worktree` pin, which is what
|
|
340
|
+
otherwise makes `main` un-checkoutable and stale branches un-deletable.
|
|
341
|
+
|
|
342
|
+
A worktree is only collapsed when it is **clean** *and* **fully pushed**.
|
|
343
|
+
Anything else is kept and reported with the reason.
|
|
344
|
+
|
|
345
|
+
## External tools
|
|
346
|
+
|
|
347
|
+
The scripts shell out to these where relevant and fail with an install hint
|
|
348
|
+
when one is missing. `che doctor` reports which are present and what needs
|
|
349
|
+
them.
|
|
350
|
+
|
|
351
|
+
<!-- che:tools -->
|
|
352
|
+
| Tool | Needed by |
|
|
353
|
+
| --- | --- |
|
|
354
|
+
| `brew` | `update-brew` |
|
|
355
|
+
| `btop` | `btop` |
|
|
356
|
+
| `diskutil` | `eject-all-drives` |
|
|
357
|
+
| `exiftool` | `remove-metadata` |
|
|
358
|
+
| `ffmpeg` | `fix-codecs` |
|
|
359
|
+
| `ffprobe` | `show-codecs`, `find-video-mkv-issues`, `scan-videos-audio-language` |
|
|
360
|
+
| `gh` | `all-actions`, `all-actions-watch` |
|
|
361
|
+
| `git` | `all-actions`, `all-actions-watch`, `checkout-my-branches`, `clean-stale-branches`, `prune-worktrees`, `sync-all-branches`, `update-from-origin`, `update-local-branches`, `update` |
|
|
362
|
+
| `mpv` | `validate-video-files` |
|
|
363
|
+
| `osascript` | `mount-all-drives` |
|
|
364
|
+
| (not a binary) | `detect-green-magenta-videos` needs OpenCV in ~/.venvs/green-magenta (override with $DETECT_GM_PYTHON) |
|
|
365
|
+
<!-- /che:tools -->
|
|
366
|
+
|
|
367
|
+
`diskutil` and `osascript` ship with macOS. The rest are one `brew install`
|
|
368
|
+
away, and `che install --install-deps` will do it for you.
|
|
369
|
+
|
|
370
|
+
`playsound-N`, the chime a wrapper plays when it finishes, is defined outside
|
|
371
|
+
this repo. When it is absent the wrappers skip it silently; `CHE_SOUNDS=0`
|
|
372
|
+
turns the chimes off everywhere.
|
|
373
|
+
|
|
374
|
+
`zip`, `bc`, `jq`, `perl`, `awk` and `sort` are no longer needed: `zipfile`,
|
|
375
|
+
integer maths, `json`, `re` and `sorted()` cover what they were doing.
|
|
376
|
+
|
|
377
|
+
## Running tests
|
|
378
|
+
|
|
379
|
+
This repo uses [Bun](https://bun.sh) as its task runner and
|
|
380
|
+
[uv](https://docs.astral.sh/uv/) to supply the dev tooling.
|
|
381
|
+
|
|
382
|
+
```sh
|
|
383
|
+
bun run test # pytest
|
|
384
|
+
bun run generate # rewrite shell/ and this README's tables from the manifest
|
|
385
|
+
bun run ci # ruff + syntax + exec bits + shebangs + generated files + pytest
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
Neither `pytest` nor `ruff` needs to be installed globally: the scripts shell
|
|
389
|
+
out to `uv run --with-requirements requirements-dev.txt`, which builds an
|
|
390
|
+
ephemeral environment per invocation. Versions are pinned exactly in
|
|
391
|
+
`requirements-dev.txt`.
|
|
392
|
+
|
|
393
|
+
(Note: plain `bun test` invokes Bun's built-in JS/TS test runner, which doesn't
|
|
394
|
+
apply here, so always use `bun run test`.)
|
|
395
|
+
|
|
396
|
+
## Troubleshooting
|
|
397
|
+
|
|
398
|
+
**A command still runs the old thing.** An alias beats a function in both zsh
|
|
399
|
+
and bash. The wrappers stash a colliding alias, define themselves, then put the
|
|
400
|
+
alias back, so your alias keeps winning at the prompt exactly as before; run
|
|
401
|
+
`\name` (backslash) to reach the wrapper, or remove the alias. `che doctor`
|
|
402
|
+
lists every collision it finds.
|
|
403
|
+
|
|
404
|
+
**`che: command not found` in a script or cron job.** Those never read an rc
|
|
405
|
+
file. Use `~/.local/bin/che`, which the installer puts on `PATH`.
|
|
406
|
+
|
|
407
|
+
**A wrapper is missing after an update.** `che update` regenerates and
|
|
408
|
+
reinstalls; `che doctor` says whether `shell/` is stale or the block is out of
|
|
409
|
+
date.
|
|
410
|
+
|
|
411
|
+
**Old hand-written wrappers are still in your rc file.** `che doctor` counts
|
|
412
|
+
them; `che install --replace-legacy` removes them, their helpers and the lines
|
|
413
|
+
that called them, after a backup.
|
|
414
|
+
|
|
415
|
+
## History
|
|
416
|
+
|
|
417
|
+
These scripts were bash until August 2026. `git log` has the shell versions;
|
|
418
|
+
the migration commit explains each deliberate behavioural change, and anywhere
|
|
419
|
+
the Python intentionally differs from the bash it replaced, there is a comment
|
|
420
|
+
at that line saying so.
|
|
421
|
+
|
|
422
|
+
The wrappers were hand-maintained in this repo's `.zshrc` until `che` arrived,
|
|
423
|
+
with the documented workflow being to copy them into `~/.zshrc` by hand. That
|
|
424
|
+
is what `che install --replace-legacy` cleans up.
|