@codebend3r/che 1.2.1 → 1.2.3
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/README.md +271 -249
- package/bin/branchsync.py +258 -11
- package/bin/commands.py +15 -1
- package/bin/git/hard-reset-all-branches.py +250 -0
- package/bin/git/prune-worktrees.py +51 -13
- package/bin/git/sync-all-branches.py +14 -9
- package/bin/gitutils.py +3 -0
- package/package.json +1 -1
- package/shell/che.bash +17 -4
- package/shell/che.fish +13 -3
- package/shell/che.zsh +17 -4
- package/shell/completions/_che +8 -4
- package/shell/completions/che.bash +5 -3
- package/shell/completions/che.fish +13 -3
package/README.md
CHANGED
|
@@ -2,14 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
# `che`
|
|
4
4
|
|
|
5
|
-
**
|
|
6
|
-
|
|
5
|
+
**Personal helper scripts, shared across machines, called by name from your
|
|
6
|
+
shell and browsable through one menu.**
|
|
7
7
|
|
|
8
8
|
[](https://github.com/codebend3r/che/actions/workflows/sanity-check.yml)
|
|
9
9
|
[](https://www.npmjs.com/package/@codebend3r/che)
|
|
10
10
|

|
|
11
11
|

|
|
12
12
|

|
|
13
|
+
[](LICENSE)
|
|
13
14
|
|
|
14
15
|
</div>
|
|
15
16
|
|
|
@@ -40,72 +41,104 @@ your shell, and browsable through one menu.**
|
|
|
40
41
|
<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">
|
|
41
42
|
</div>
|
|
42
43
|
|
|
43
|
-
|
|
44
|
-
(`delete-by-ext`, `sync-all-branches`, `show-codecs`, …) are shell functions
|
|
45
|
-
generated from one manifest, `bin/commands.py`, and installed into whichever
|
|
46
|
-
shells you use.
|
|
44
|
+
<br>
|
|
47
45
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
```
|
|
46
|
+
`che` is a set of standalone Python scripts for git housekeeping, media
|
|
47
|
+
libraries, file cleanup, NAS drives and system upkeep. Each one is installed
|
|
48
|
+
as a shell function in zsh, bash and fish, and all of them can be browsed,
|
|
49
|
+
filtered and run from a full-screen terminal menu.
|
|
53
50
|
|
|
54
51
|
<!-- che:count -->
|
|
55
|
-
|
|
52
|
+
34 commands across 5 categories, plus 6 built into `che` itself
|
|
56
53
|
<!-- /che:count -->
|
|
57
54
|
|
|
58
|
-
##
|
|
55
|
+
## Contents
|
|
56
|
+
|
|
57
|
+
- [Features](#features)
|
|
58
|
+
- [Quick start](#quick-start)
|
|
59
|
+
- [Installation](#installation)
|
|
60
|
+
- [Usage](#usage)
|
|
61
|
+
- [Commands](#commands)
|
|
62
|
+
- [Configuration](#configuration)
|
|
63
|
+
- [External tools](#external-tools)
|
|
64
|
+
- [Development](#development)
|
|
65
|
+
- [Troubleshooting](#troubleshooting)
|
|
66
|
+
- [License](#license)
|
|
67
|
+
|
|
68
|
+
## Features
|
|
59
69
|
|
|
60
70
|
- **One manifest, everything generated.** Wrappers, completions, the menu, the
|
|
61
|
-
doctor report and the tables in this README all come from
|
|
62
|
-
`
|
|
63
|
-
- **
|
|
64
|
-
that only prints
|
|
65
|
-
- **Standard library only.**
|
|
66
|
-
no `pip install
|
|
71
|
+
doctor report and the tables in this README all come from
|
|
72
|
+
`bin/commands.py`. CI fails if any of them drift.
|
|
73
|
+
- **Safe by default.** Destructive commands preview unless told otherwise, and
|
|
74
|
+
each has a `-dr` twin that only prints. The menu opens on the dry run.
|
|
75
|
+
- **Standard library only.** Any Python 3.12+ runs everything. No virtualenv,
|
|
76
|
+
no `pip install`.
|
|
67
77
|
- **Your dotfiles stay yours.** The installer writes a few lines between
|
|
68
|
-
markers, backs every rc file
|
|
69
|
-
|
|
70
|
-
-
|
|
71
|
-
blocks, external tools
|
|
78
|
+
markers, backs up every rc file first, and never touches anything outside
|
|
79
|
+
them.
|
|
80
|
+
- **Self-diagnosing.** `che doctor` checks the interpreter, generated files, rc
|
|
81
|
+
blocks, external tools and alias collisions, and exits non-zero on failure.
|
|
72
82
|
|
|
73
|
-
##
|
|
83
|
+
## Quick start
|
|
74
84
|
|
|
75
|
-
|
|
85
|
+
```sh
|
|
86
|
+
curl -fsSL https://raw.githubusercontent.com/codebend3r/che/main/install.sh | bash
|
|
87
|
+
exec $SHELL
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
```sh
|
|
91
|
+
che # browse everything: type to filter, Enter to run
|
|
92
|
+
che <command> [args…] # run one command directly
|
|
93
|
+
delete-by-ext --path=… # or call any command by name
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Installation
|
|
97
|
+
|
|
98
|
+
### Requirements
|
|
99
|
+
|
|
100
|
+
- **Python 3.12 or newer.** The installer picks the newest suitable interpreter
|
|
101
|
+
it can find and records its path, so an old system `python3` is fine as long
|
|
102
|
+
as a newer one exists (`brew install python@3.13` if not).
|
|
103
|
+
- **zsh, bash or fish.**
|
|
104
|
+
- Some commands need external tools such as `ffmpeg` or `gh`; see
|
|
105
|
+
[External tools](#external-tools).
|
|
106
|
+
|
|
107
|
+
### Install methods
|
|
108
|
+
|
|
109
|
+
**From a clone**
|
|
76
110
|
|
|
77
111
|
```sh
|
|
78
112
|
git clone https://github.com/codebend3r/che.git ~/Developer/git/che
|
|
79
113
|
~/Developer/git/che/install.sh
|
|
80
114
|
```
|
|
81
115
|
|
|
82
|
-
|
|
83
|
-
`CHE_HOME` points):
|
|
116
|
+
**One-liner** (clones into `~/Developer/git/che`, or `$CHE_HOME` if set)
|
|
84
117
|
|
|
85
118
|
```sh
|
|
86
119
|
curl -fsSL https://raw.githubusercontent.com/codebend3r/che/main/install.sh | bash
|
|
87
120
|
```
|
|
88
121
|
|
|
89
|
-
|
|
122
|
+
**From npm**
|
|
90
123
|
|
|
91
124
|
```sh
|
|
92
125
|
npm install -g @codebend3r/che
|
|
93
|
-
che
|
|
126
|
+
che # offers to install the shell wrappers on first run
|
|
94
127
|
```
|
|
95
128
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
129
|
+
> [!NOTE]
|
|
130
|
+
> Install the npm package globally rather than running it through `npx`: the
|
|
131
|
+
> wrappers point back at the package's location, and the `npx` cache is
|
|
132
|
+
> temporary. Update with `npm install -g @codebend3r/che@latest`; `che update`
|
|
133
|
+
> only works in a git clone.
|
|
134
|
+
|
|
135
|
+
### First run
|
|
100
136
|
|
|
101
|
-
`install.sh` finds a suitable
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
`brew install` them when Homebrew is present). A question already answered on
|
|
107
|
-
the command line (`--no-sounds`, `--no-path-shim`, `--replace-legacy`,
|
|
108
|
-
`--install-deps`) is not asked again.
|
|
137
|
+
`install.sh` finds a suitable Python and hands over to a short wizard. It
|
|
138
|
+
lists the shells it found and the rc files it would touch, asks whether to put
|
|
139
|
+
`che` on `PATH` and whether to keep the completion chimes, offers to remove
|
|
140
|
+
hand-written wrappers from before the installer existed, and reports missing
|
|
141
|
+
external tools (offering to `brew install` them when Homebrew is present).
|
|
109
142
|
|
|
110
143
|
<div align="center">
|
|
111
144
|
<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">
|
|
@@ -113,29 +146,31 @@ the command line (`--no-sounds`, `--no-path-shim`, `--replace-legacy`,
|
|
|
113
146
|
<em>Running <code>che</code> before installing offers to set itself up.</em>
|
|
114
147
|
</div>
|
|
115
148
|
|
|
116
|
-
|
|
149
|
+
### Unattended install
|
|
150
|
+
|
|
151
|
+
Any question answered on the command line is not asked again
|
|
152
|
+
(`--no-sounds`, `--no-path-shim`, `--replace-legacy`, `--install-deps`).
|
|
117
153
|
|
|
118
154
|
```sh
|
|
119
|
-
./install.sh --yes
|
|
120
|
-
./install.sh --yes --shells=zsh,fish
|
|
121
|
-
./install.sh --dry-run
|
|
122
|
-
./install.sh --yes --replace-legacy
|
|
123
|
-
./install.sh --print
|
|
155
|
+
./install.sh --yes # install into every detected shell
|
|
156
|
+
./install.sh --yes --shells=zsh,fish # or only the shells you name
|
|
157
|
+
./install.sh --dry-run # report what would change, write nothing
|
|
158
|
+
./install.sh --yes --replace-legacy # also remove pre-che hand-written wrappers
|
|
159
|
+
./install.sh --print # print the rc block and exit
|
|
124
160
|
```
|
|
125
161
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
### What lands where
|
|
162
|
+
### What gets installed
|
|
129
163
|
|
|
130
|
-
|
|
|
164
|
+
| What | Where |
|
|
131
165
|
| --- | --- |
|
|
132
|
-
| The rc block
|
|
133
|
-
|
|
|
134
|
-
| Tab completions | `shell/completions/`, sourced by the
|
|
135
|
-
| A `che` executable
|
|
136
|
-
|
|
|
137
|
-
|
|
138
|
-
|
|
166
|
+
| The rc block | `~/.zshrc` (or `$ZDOTDIR/.zshrc`), `~/.bashrc` and `~/.bash_profile` (whichever exist), `~/.config/fish/config.fish`, and `~/.profile` only when `sh` is requested |
|
|
167
|
+
| Shell wrappers | `shell/che.zsh`, `shell/che.bash`, `shell/che.fish` in the repo |
|
|
168
|
+
| Tab completions | `shell/completions/`, sourced by the wrapper file |
|
|
169
|
+
| A `che` executable for cron and scripts | `~/.local/bin/che` |
|
|
170
|
+
| Your installer answers | `~/.config/che/config.json` |
|
|
171
|
+
| rc file backups | `~/.config/che/backups/` |
|
|
172
|
+
|
|
173
|
+
The rc block is deliberately small, so upgrading `che` never rewrites your
|
|
139
174
|
dotfiles:
|
|
140
175
|
|
|
141
176
|
```zsh
|
|
@@ -148,37 +183,28 @@ case ":$PATH:" in *":/Users/you/.local/bin:"*) ;; *) PATH="/Users/you/.local/bin
|
|
|
148
183
|
# <<< che shell functions <<<
|
|
149
184
|
```
|
|
150
185
|
|
|
151
|
-
Everything between
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
Then reload:
|
|
155
|
-
|
|
156
|
-
```sh
|
|
157
|
-
exec zsh # or exec bash / exec fish
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
**Requires Python 3.12+** and nothing else to run the scripts. The installer
|
|
161
|
-
picks the newest suitable interpreter it can find and records its path in the
|
|
162
|
-
block, so an old `/usr/bin/python3` does not matter as long as some newer
|
|
163
|
-
python3 exists. `brew install python@3.13` if none does.
|
|
186
|
+
Everything between the markers is rewritten on every install; everything
|
|
187
|
+
outside them is left alone. Reload your shell afterwards with `exec zsh`
|
|
188
|
+
(or `exec bash`, `exec fish`).
|
|
164
189
|
|
|
165
|
-
###
|
|
190
|
+
### Updating and uninstalling
|
|
166
191
|
|
|
167
192
|
```sh
|
|
168
|
-
che
|
|
193
|
+
che update # pull the latest scripts and refresh the wrappers
|
|
194
|
+
che uninstall # remove the rc block, the shim and the completions
|
|
169
195
|
che uninstall --purge # also delete ~/.config/che and the menu's input history
|
|
170
196
|
```
|
|
171
197
|
|
|
172
|
-
|
|
198
|
+
Uninstalling leaves the repository in place; delete the directory to finish.
|
|
173
199
|
|
|
174
|
-
##
|
|
200
|
+
## Usage
|
|
175
201
|
|
|
176
|
-
|
|
177
|
-
default**, so pressing Enter on `delete-by-ext` shows you what it would delete
|
|
178
|
-
rather than deleting it.
|
|
202
|
+
### The menu
|
|
179
203
|
|
|
180
|
-
|
|
181
|
-
the selected command's summary, script, required tools and flags
|
|
204
|
+
Run `che` with no arguments to open the menu. Type to filter; the right pane
|
|
205
|
+
shows the selected command's summary, script, required tools and flags.
|
|
206
|
+
Anything that can preview **previews by default**, so pressing Enter on
|
|
207
|
+
`delete-by-ext` shows what it would delete rather than deleting it.
|
|
182
208
|
|
|
183
209
|
<div align="center">
|
|
184
210
|
<img src="search.png" alt="The menu filtered by the search term branch, showing the five matching commands" width="850">
|
|
@@ -198,19 +224,28 @@ the selected command's summary, script, required tools and flags:
|
|
|
198
224
|
| `q` | quit |
|
|
199
225
|
<!-- /che:keys -->
|
|
200
226
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
227
|
+
### From the shell
|
|
228
|
+
|
|
229
|
+
Every command is a plain shell function, so `type delete-by-ext` shows exactly
|
|
230
|
+
what it runs. Destructive commands have a `-dr` twin that only prints:
|
|
204
231
|
|
|
205
232
|
```sh
|
|
206
233
|
delete-by-ext --path=/Volumes/Media --ext=nfo,txt # deletes
|
|
207
234
|
delete-by-ext-dr --path=/Volumes/Media --ext=nfo # shows what it would delete
|
|
208
235
|
```
|
|
209
236
|
|
|
210
|
-
`che <command>` runs the same
|
|
211
|
-
|
|
237
|
+
`che <command>` runs the same script with the same environment its wrapper
|
|
238
|
+
uses, and `che help <command>` prints that script's `--help`.
|
|
239
|
+
|
|
240
|
+
All scripts share the same CLI conventions:
|
|
241
|
+
|
|
242
|
+
- **Long flags only**, with `=` for values: `--path=/foo`, `--ext=nfo,txt`.
|
|
243
|
+
- **Booleans** accept a bare `--flag` or `--flag=true|false` (also
|
|
244
|
+
`yes|no`, `1|0`, `on|off`). A bare flag never swallows the next argument.
|
|
245
|
+
- **`--help`** on every script.
|
|
246
|
+
- **A symlinked `--path` is refused.** Pass the real path to act on it.
|
|
212
247
|
|
|
213
|
-
|
|
248
|
+
## Commands
|
|
214
249
|
|
|
215
250
|
<!-- che:commands -->
|
|
216
251
|
| Command | Does | Preview |
|
|
@@ -220,8 +255,9 @@ have used, and `che help <command>` prints that script's own `--help`.
|
|
|
220
255
|
| `all-actions-watch` | Same as all-actions, refreshed on an interval | |
|
|
221
256
|
| `checkout-my-branches` | Check out recent remote branches you authored that aren't local yet | |
|
|
222
257
|
| `clean-stale-branches` | Delete local branches whose upstream is gone | `clean-stale-branches-dr` |
|
|
258
|
+
| `hard-reset-all-branches` | Check out every local branch and hard reset it to origin/<branch> | `hard-reset-all-branches-dr` |
|
|
223
259
|
| `prune-worktrees` | Remove every linked worktree, then prune stale admin records | `prune-worktrees-dr` |
|
|
224
|
-
| `sync-all-branches` | Update every branch and worktree,
|
|
260
|
+
| `sync-all-branches` | Update every branch and worktree, pruning what has been merged | `sync-all-branches-dr` |
|
|
225
261
|
| `update-from-origin` | Alias of sync-all-branches | `--dry-run` |
|
|
226
262
|
| `update-local-branches` | Rebase every local branch that has an upstream onto origin | `--dry-run` |
|
|
227
263
|
| **Video** | *Codecs, renaming, dedupe and metadata* | |
|
|
@@ -255,12 +291,11 @@ have used, and `che help <command>` prints that script's own `--help`.
|
|
|
255
291
|
| `btop` | Launch btop with a gruvbox theme matching the macOS appearance | |
|
|
256
292
|
<!-- /che:commands -->
|
|
257
293
|
|
|
258
|
-
Two commands have no `-dr` twin. `remove-metadata` cannot preview
|
|
259
|
-
exiftool rewrites in place. `rename-video-file`
|
|
260
|
-
|
|
261
|
-
`--dry-run=false`.
|
|
294
|
+
Two commands have no `-dr` twin. `remove-metadata` cannot preview, since
|
|
295
|
+
exiftool rewrites files in place. `rename-video-file` already defaults to
|
|
296
|
+
`--dry-run=true`, so the real rename is the one that needs `--dry-run=false`.
|
|
262
297
|
|
|
263
|
-
###
|
|
298
|
+
### Built-in commands
|
|
264
299
|
|
|
265
300
|
<!-- che:builtins -->
|
|
266
301
|
| Command | Does |
|
|
@@ -273,10 +308,11 @@ defaults to `--dry-run=true`, so the real rename is the one that needs
|
|
|
273
308
|
| `che completions` | Print the completion script for a shell |
|
|
274
309
|
<!-- /che:builtins -->
|
|
275
310
|
|
|
276
|
-
`che doctor` checks the interpreter, the
|
|
277
|
-
current, whether each rc file has the block, whether `che` is on `PATH`,
|
|
278
|
-
external tool, and anything shadowing a wrapper. It exits non-zero on a
|
|
279
|
-
failure, so it
|
|
311
|
+
`che doctor` checks the interpreter, the repository, whether generated files
|
|
312
|
+
are current, whether each rc file has the block, whether `che` is on `PATH`,
|
|
313
|
+
every external tool, and anything shadowing a wrapper. It exits non-zero on a
|
|
314
|
+
real failure, so it can serve as a health check; `che doctor --json` gives
|
|
315
|
+
machine-readable output.
|
|
280
316
|
|
|
281
317
|
<div align="center">
|
|
282
318
|
<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">
|
|
@@ -284,127 +320,61 @@ failure, so it works in a health check. `che doctor --json` for a machine.
|
|
|
284
320
|
<em>Every problem <code>che doctor</code> reports comes with the command that fixes it.</em>
|
|
285
321
|
</div>
|
|
286
322
|
|
|
287
|
-
###
|
|
288
|
-
|
|
289
|
-
| Variable | Effect |
|
|
290
|
-
| --- | --- |
|
|
291
|
-
| `CHE_HOME` | Where this repo lives. Set by the block; the wrapper file falls back to its own location. |
|
|
292
|
-
| `CHE_PYTHON` | Interpreter the wrappers use. Set by the block to the one the installer verified. |
|
|
293
|
-
| `CHE_SOUNDS` | `0` turns off the `playsound-N` chime after every command. |
|
|
294
|
-
| `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. |
|
|
295
|
-
| `NO_COLOR` | Honoured by every script and by the menu. |
|
|
296
|
-
|
|
297
|
-
## How it fits together
|
|
298
|
-
|
|
299
|
-
`bin/commands.py` is the single source of truth. The wrappers, the completions,
|
|
300
|
-
the menu, the `doctor` report and the tables in this README are all generated
|
|
301
|
-
from it, and `bun run ci` fails if any of them drift.
|
|
302
|
-
|
|
303
|
-
### Adding a command
|
|
304
|
-
|
|
305
|
-
1. Write the script under `bin/<category>/` and `chmod +x` it.
|
|
306
|
-
2. Add a `Command(...)` to `bin/commands.py`: name, summary, script, sound,
|
|
307
|
-
whether it can preview, which external tools it needs, its flags, and the
|
|
308
|
-
two or three questions the menu should ask.
|
|
309
|
-
3. `bun run generate` to rewrite `shell/` and this README's tables, and commit
|
|
310
|
-
the result.
|
|
311
|
-
4. `che install` on each machine, or `che update`, which does both.
|
|
312
|
-
|
|
313
|
-
There is no step where you hand-edit a wrapper into `~/.zshrc`, and no step
|
|
314
|
-
where you copy one machine's rc file to another.
|
|
315
|
-
|
|
316
|
-
### Layout
|
|
317
|
-
|
|
318
|
-
```
|
|
319
|
-
bin/
|
|
320
|
-
art.py the wordmark and the face beside it, for every screen
|
|
321
|
-
che.py the dispatcher, and the loop that runs what the menu picks
|
|
322
|
-
commands.py the command manifest: the source of truth for everything
|
|
323
|
-
doctor.py `che doctor`: every check on the install
|
|
324
|
-
gitutils.py shared git helpers: worktrees, main branch, hook skipping
|
|
325
|
-
install.py installer, first-run wizard, self-update, uninstall
|
|
326
|
-
machine.py shells, rc file paths, python, config: facts about the machine
|
|
327
|
-
media.py shared ffprobe helpers for the video scripts
|
|
328
|
-
menu.py the menu's list, keys and drawing
|
|
329
|
-
nas.py the NAS drive table and SMB mounting, for the drive scripts
|
|
330
|
-
rcfile.py edits to rc files: the che block and old hand-written wrappers
|
|
331
|
-
shellgen.py renders the wrappers, completions and README tables
|
|
332
|
-
tui.py terminal primitives: raw input, frame buffer, prompts
|
|
333
|
-
utils.py shared library: logging, arg parsing, subprocess, files
|
|
334
|
-
drives/ mount, eject, and keep-alive for the NAS volumes
|
|
335
|
-
files/ delete/compress/inspect files by extension or size
|
|
336
|
-
git/ branch hygiene, worktrees, GitHub Actions status
|
|
337
|
-
system/ Homebrew updates, btop launcher
|
|
338
|
-
video/ codec inspection, renaming, dedupe, metadata
|
|
339
|
-
shell/ GENERATED wrappers and completions, do not edit
|
|
340
|
-
test/ pytest suites
|
|
341
|
-
install.sh bootstrap for a machine with nothing on it
|
|
342
|
-
.zshrc a shim that sources shell/che.zsh, kept for old setups
|
|
343
|
-
```
|
|
344
|
-
|
|
345
|
-
## CLI conventions
|
|
323
|
+
### Syncing branches
|
|
346
324
|
|
|
347
|
-
|
|
348
|
-
`
|
|
349
|
-
|
|
350
|
-
- **Long flags only**, `--name=value` for values.
|
|
351
|
-
- **Booleans** accept a bare `--flag` *and* `--flag=true|false` (plus
|
|
352
|
-
`yes|no`, `1|0`, `on|off`). A bare `--flag` never swallows the next argument.
|
|
353
|
-
- **`--help`** on every script.
|
|
354
|
-
- **Destructive scripts default to dry-run.** `delete-by-ext`,
|
|
355
|
-
`delete-empty-folders`, `delete-smb-files`, `files-under-size`, `fix-codecs`,
|
|
356
|
-
`delete-duplicate-videos` and `prune-worktrees` read `DRY_RUN` and preview
|
|
357
|
-
unless told otherwise; the generated wrapper sets `DRY_RUN=false` for the
|
|
358
|
-
real thing and the `-dr` twin sets it back to `true`.
|
|
359
|
-
- **A symlinked `--path` is refused.** `find` never followed a symlinked start
|
|
360
|
-
point, so pointing a delete tool at `~/media -> /Volumes/...` used to be a
|
|
361
|
-
silent no-op. Pass the real path to act on it.
|
|
362
|
-
|
|
363
|
-
## Syncing branches
|
|
325
|
+
`sync-all-branches` (alias `update-from-origin`) fetches once and rebases
|
|
326
|
+
every local branch onto its upstream, falling back to `origin/<branch>`.
|
|
364
327
|
|
|
365
328
|
```sh
|
|
366
|
-
sync-all-branches # update
|
|
367
|
-
sync-all-branches-dr # preview cached remote refs; no fetch
|
|
368
|
-
update-from-origin # alias of sync-all-branches
|
|
329
|
+
sync-all-branches # update every branch and worktree
|
|
330
|
+
sync-all-branches-dr # preview against cached remote refs; no fetch, no changes
|
|
369
331
|
```
|
|
370
332
|
|
|
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
|
-
Like
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
333
|
+
- **Worktrees update in place.** Branches checked out in other worktrees,
|
|
334
|
+
including locked ones, rebase inside their own checkout. Unoccupied branches
|
|
335
|
+
use temporary worktrees, so your current checkout never switches.
|
|
336
|
+
- **Local work is preserved.** Uncommitted tracked and untracked changes are
|
|
337
|
+
stashed and restored with their staged state; existing stashes are
|
|
338
|
+
untouched. Dirty submodules and in-progress Git operations are reported and
|
|
339
|
+
left alone.
|
|
340
|
+
- **No local change is discarded.** It never pushes. A branch is only moved
|
|
341
|
+
or deleted when Git can prove its changes already exist elsewhere:
|
|
342
|
+
- A diverged branch whose changes are all in its upstream (a stale copy of
|
|
343
|
+
a branch rebased and force-pushed from another machine, typically after
|
|
344
|
+
its parent was squash-merged) moves to the upstream instead of replaying
|
|
345
|
+
commits that would conflict.
|
|
346
|
+
- A branch whose upstream is gone is deleted, with its clean worktree (a
|
|
347
|
+
lock is dropped first), once it is proven merged into `origin/main`,
|
|
348
|
+
`master`, `dev` or `develop`: contained in it, its content already there
|
|
349
|
+
(squash merges included), or a merged GitHub PR. The tip SHA is printed so
|
|
350
|
+
you can restore it. A missing upstream alone is not treated as merged: an
|
|
351
|
+
unproven one is listed as `Upstream gone` and kept. A merged branch whose
|
|
352
|
+
worktree is dirty, mid-operation or your current checkout is kept and
|
|
353
|
+
reported.
|
|
354
|
+
- **Failures are isolated.** A rebase conflict is aborted and reported while
|
|
355
|
+
other branches continue, and any blocked branch makes the command exit
|
|
356
|
+
non-zero. If restoring work conflicts, the recovery stash ID and worktree
|
|
357
|
+
path are printed.
|
|
358
|
+
- New remote branches you authored become local tracking branches.
|
|
359
|
+
|
|
360
|
+
Like every branch-touching script in `bin/git/`, it disables repository hooks
|
|
361
|
+
for its own Git commands. The legacy `--no-push` and `--keep-locked` flags are
|
|
362
|
+
still accepted and ignored.
|
|
363
|
+
|
|
364
|
+
## Configuration
|
|
365
|
+
|
|
366
|
+
| Variable | Effect |
|
|
367
|
+
| --- | --- |
|
|
368
|
+
| `CHE_HOME` | Where the repository lives. Set by the rc block; the wrapper file falls back to its own location. |
|
|
369
|
+
| `CHE_PYTHON` | The interpreter the wrappers use. Set by the rc block to the one the installer verified. |
|
|
370
|
+
| `CHE_SOUNDS` | Set to `0` to turn off the `playsound-N` chime after each command. |
|
|
371
|
+
| `DRY_RUN` | Set explicitly by the wrappers on every call, so exporting it only affects scripts run directly. |
|
|
372
|
+
| `NO_COLOR` | Disables color in every script and in the menu. |
|
|
402
373
|
|
|
403
374
|
## External tools
|
|
404
375
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
them.
|
|
376
|
+
Scripts that shell out to an external tool fail with an install hint when it
|
|
377
|
+
is missing, and `che doctor` reports which are present.
|
|
408
378
|
|
|
409
379
|
<!-- che:tools -->
|
|
410
380
|
| Tool | Needed by |
|
|
@@ -416,67 +386,119 @@ them.
|
|
|
416
386
|
| `ffmpeg` | `fix-codecs` |
|
|
417
387
|
| `ffprobe` | `show-codecs`, `find-video-mkv-issues`, `scan-videos-audio-language` |
|
|
418
388
|
| `gh` | `all-actions`, `all-actions-watch` |
|
|
419
|
-
| `git` | `all-actions`, `all-actions-watch`, `checkout-my-branches`, `clean-stale-branches`, `prune-worktrees`, `sync-all-branches`, `update-from-origin`, `update-local-branches`, `update` |
|
|
389
|
+
| `git` | `all-actions`, `all-actions-watch`, `checkout-my-branches`, `clean-stale-branches`, `hard-reset-all-branches`, `prune-worktrees`, `sync-all-branches`, `update-from-origin`, `update-local-branches`, `update` |
|
|
420
390
|
| `mpv` | `validate-video-files` |
|
|
421
391
|
| `osascript` | `mount-all-drives` |
|
|
422
392
|
| (not a binary) | `detect-green-magenta-videos` needs OpenCV in ~/.venvs/green-magenta (override with $DETECT_GM_PYTHON) |
|
|
423
393
|
<!-- /che:tools -->
|
|
424
394
|
|
|
425
|
-
`diskutil` and `osascript` ship with macOS. The rest are
|
|
426
|
-
away, and `che install --install-deps`
|
|
395
|
+
`diskutil` and `osascript` ship with macOS. The rest are a `brew install`
|
|
396
|
+
away, and `che install --install-deps` installs them for you.
|
|
427
397
|
|
|
428
|
-
`playsound-N`, the chime a wrapper
|
|
429
|
-
this
|
|
430
|
-
turns the chimes off everywhere.
|
|
398
|
+
`playsound-N`, the chime played when a wrapper finishes, is defined outside
|
|
399
|
+
this repository; the wrappers skip it silently when it is absent.
|
|
431
400
|
|
|
432
|
-
|
|
433
|
-
integer maths, `json`, `re` and `sorted()` cover what they were doing.
|
|
401
|
+
## Development
|
|
434
402
|
|
|
435
|
-
|
|
403
|
+
`bin/commands.py` is the single source of truth. The wrappers, completions,
|
|
404
|
+
menu, doctor report and this README's tables are all generated from it.
|
|
436
405
|
|
|
437
|
-
|
|
438
|
-
[uv](https://docs.astral.sh/uv/) to supply the dev tooling.
|
|
406
|
+
### Adding a command
|
|
439
407
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
408
|
+
1. Write the script under `bin/<category>/` and `chmod +x` it.
|
|
409
|
+
2. Add a `Command(...)` to `bin/commands.py`: name, summary, script, sound,
|
|
410
|
+
preview style, required tools, flags, and the two or three questions the
|
|
411
|
+
menu should ask.
|
|
412
|
+
3. Run `bun run generate` to rewrite `shell/` and the README tables, and
|
|
413
|
+
commit the result.
|
|
414
|
+
4. Run `che update` on each machine.
|
|
415
|
+
|
|
416
|
+
Conventions for new scripts are documented at the top of `bin/utils.py` and in
|
|
417
|
+
[`CLAUDE.md`](CLAUDE.md).
|
|
418
|
+
|
|
419
|
+
### Project layout
|
|
420
|
+
|
|
421
|
+
```
|
|
422
|
+
bin/
|
|
423
|
+
che.py dispatcher, and the loop that runs what the menu picks
|
|
424
|
+
commands.py command manifest: the source of truth for everything
|
|
425
|
+
menu.py the menu's model, keys and drawing
|
|
426
|
+
shellgen.py renders wrappers, completions and README tables
|
|
427
|
+
install.py installer, first-run wizard, self-update, uninstall
|
|
428
|
+
doctor.py `che doctor`
|
|
429
|
+
rcfile.py rc file edits: the che block and legacy wrappers
|
|
430
|
+
machine.py shells, rc paths, python discovery, install record
|
|
431
|
+
tui.py terminal primitives: raw input, frame buffer, prompts
|
|
432
|
+
art.py the wordmark and banner
|
|
433
|
+
utils.py shared library: logging, arg parsing, subprocess, files
|
|
434
|
+
gitutils.py git helpers: worktrees, main branch, hook skipping
|
|
435
|
+
branchsync.py per-branch sync used by sync-all-branches
|
|
436
|
+
media.py ffprobe helpers for the video scripts
|
|
437
|
+
nas.py NAS drive table and SMB mounting
|
|
438
|
+
git/ video/ files/ drives/ system/ the scripts, by category
|
|
439
|
+
shell/ GENERATED wrappers and completions (do not edit)
|
|
440
|
+
test/ pytest suites
|
|
441
|
+
install.sh bootstrap for a bare machine
|
|
444
442
|
```
|
|
445
443
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
444
|
+
### Testing
|
|
445
|
+
|
|
446
|
+
The repository uses [Bun](https://bun.sh) as its task runner and
|
|
447
|
+
[uv](https://docs.astral.sh/uv/) for dev tooling, so neither `pytest` nor
|
|
448
|
+
`ruff` needs to be installed globally. Versions are pinned in
|
|
449
449
|
`requirements-dev.txt`.
|
|
450
450
|
|
|
451
|
-
|
|
452
|
-
|
|
451
|
+
```sh
|
|
452
|
+
bun run test # pytest
|
|
453
|
+
bun run generate # rewrite shell/ and the README tables from the manifest
|
|
454
|
+
bun run ci # ruff, exec bits, shebangs, pytest; fails on stale generated files
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
> [!TIP]
|
|
458
|
+
> Use `bun run test`, not `bun test`, which runs Bun's built-in JavaScript
|
|
459
|
+
> test runner instead.
|
|
453
460
|
|
|
454
461
|
## Troubleshooting
|
|
455
462
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
alias back, so your alias keeps winning at the prompt exactly as before; run
|
|
459
|
-
`\name` (backslash) to reach the wrapper, or remove the alias. `che doctor`
|
|
460
|
-
lists every collision it finds.
|
|
463
|
+
<details>
|
|
464
|
+
<summary><strong>A command still runs the old version</strong></summary>
|
|
461
465
|
|
|
462
|
-
|
|
463
|
-
|
|
466
|
+
An alias beats a function in zsh and bash. The wrappers preserve any colliding
|
|
467
|
+
alias, so it keeps winning at the prompt. Run `\name` to reach the wrapper, or
|
|
468
|
+
remove the alias. `che doctor` lists every collision.
|
|
464
469
|
|
|
465
|
-
|
|
466
|
-
reinstalls; `che doctor` says whether `shell/` is stale or the block is out of
|
|
467
|
-
date.
|
|
470
|
+
</details>
|
|
468
471
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
+
<details>
|
|
473
|
+
<summary><strong><code>che: command not found</code> in a script or cron job</strong></summary>
|
|
474
|
+
|
|
475
|
+
Non-interactive shells never read an rc file. Use `~/.local/bin/che`, which
|
|
476
|
+
the installer puts on `PATH`.
|
|
477
|
+
|
|
478
|
+
</details>
|
|
479
|
+
|
|
480
|
+
<details>
|
|
481
|
+
<summary><strong>A wrapper is missing after an update</strong></summary>
|
|
482
|
+
|
|
483
|
+
`che update` regenerates and reinstalls the wrappers. `che doctor` reports
|
|
484
|
+
whether `shell/` is stale or the rc block is out of date.
|
|
485
|
+
|
|
486
|
+
</details>
|
|
487
|
+
|
|
488
|
+
<details>
|
|
489
|
+
<summary><strong>Old hand-written wrappers are still in an rc file</strong></summary>
|
|
490
|
+
|
|
491
|
+
`che doctor` counts them, and `che install --replace-legacy` removes them,
|
|
492
|
+
along with their helpers and the lines that called them, after a backup.
|
|
493
|
+
|
|
494
|
+
</details>
|
|
472
495
|
|
|
473
496
|
## History
|
|
474
497
|
|
|
475
|
-
These scripts were bash until August 2026
|
|
476
|
-
|
|
477
|
-
the
|
|
478
|
-
|
|
498
|
+
These scripts were written in bash until August 2026, when they were ported to
|
|
499
|
+
Python. Every intentional behavioural difference from the bash originals is
|
|
500
|
+
commented at the line where it occurs, and `git log` holds the shell versions.
|
|
501
|
+
|
|
502
|
+
## License
|
|
479
503
|
|
|
480
|
-
|
|
481
|
-
with the documented workflow being to copy them into `~/.zshrc` by hand. That
|
|
482
|
-
is what `che install --replace-legacy` cleans up.
|
|
504
|
+
[MIT](LICENSE) © CJ Rivas
|