xcfm 0.1.0__tar.gz
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.
- xcfm-0.1.0/LICENSE +21 -0
- xcfm-0.1.0/PKG-INFO +340 -0
- xcfm-0.1.0/README.md +326 -0
- xcfm-0.1.0/pyproject.toml +22 -0
- xcfm-0.1.0/setup.cfg +4 -0
- xcfm-0.1.0/xcfm.egg-info/PKG-INFO +340 -0
- xcfm-0.1.0/xcfm.egg-info/SOURCES.txt +9 -0
- xcfm-0.1.0/xcfm.egg-info/dependency_links.txt +1 -0
- xcfm-0.1.0/xcfm.egg-info/entry_points.txt +2 -0
- xcfm-0.1.0/xcfm.egg-info/requires.txt +5 -0
- xcfm-0.1.0/xcfm.egg-info/top_level.txt +1 -0
xcfm-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alexander Demin
|
|
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.
|
xcfm-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: xcfm
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Two-panel console file manager
|
|
5
|
+
Requires-Python: >=3.11
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Dist: boto3
|
|
9
|
+
Requires-Dist: google-cloud-storage
|
|
10
|
+
Requires-Dist: oci
|
|
11
|
+
Requires-Dist: google-api-python-client
|
|
12
|
+
Requires-Dist: google-auth
|
|
13
|
+
Dynamic: license-file
|
|
14
|
+
|
|
15
|
+
# xc
|
|
16
|
+
|
|
17
|
+
A two-panel console file manager inspired by Midnight Commander, written in Python.
|
|
18
|
+
|
|
19
|
+

|
|
20
|
+
|
|
21
|
+
## Intro
|
|
22
|
+
|
|
23
|
+
The Python version of xc is a single self-contained script (`xc.py`) that runs via [uv](https://docs.astral.sh/uv/). This means:
|
|
24
|
+
|
|
25
|
+
- **Zero setup** -- no virtualenv, no `pip install`, no `requirements.txt`. Just run `uv run xc.py`.
|
|
26
|
+
- **Inline dependencies** -- the script header declares its own dependencies (`boto3`, `google-cloud-storage`, `oci`, `google-api-python-client`), and uv resolves and caches them automatically on the first run.
|
|
27
|
+
- **Reproducible** -- uv pins the Python version (`>=3.11`) and handles isolation, so the script works the same way on any machine.
|
|
28
|
+
- **Single file to deploy** -- copy `xc.py` to a server, a dotfiles repo, or a USB stick. There is nothing else to carry.
|
|
29
|
+
|
|
30
|
+
### Installing uv
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
# macOS / Linux
|
|
34
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
35
|
+
|
|
36
|
+
# Homebrew
|
|
37
|
+
brew install uv
|
|
38
|
+
|
|
39
|
+
# Windows
|
|
40
|
+
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
After installing, run the file manager with:
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
uv run xc.py
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The script has a shebang line, so you can rename it, make it executable, and put it on your PATH:
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
cp xc.py ~/.local/bin/xc
|
|
53
|
+
chmod +x ~/.local/bin/xc
|
|
54
|
+
xc
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Development
|
|
58
|
+
|
|
59
|
+
The `pyproject.toml` in the repository is only used for local development tooling (e.g. `black` formatter settings). It is **not** needed to run xc -- `xc.py` is fully self-contained with its own inline dependency declarations.
|
|
60
|
+
|
|
61
|
+
### Self-update
|
|
62
|
+
|
|
63
|
+
To update xc to the latest version from GitHub:
|
|
64
|
+
|
|
65
|
+
```sh
|
|
66
|
+
xc -u
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
This fetches the latest `xc.py` from the repository, compares versions, and replaces the current binary if a newer version is available. The previous version is saved as `xc.prev` next to the executable.
|
|
70
|
+
|
|
71
|
+
## User manual
|
|
72
|
+
|
|
73
|
+
### Dual-panel concept
|
|
74
|
+
|
|
75
|
+
xc shows two file panels side by side. One panel is **active** (highlighted border), the other is **inactive**. You navigate files in the active panel and use the inactive panel as a target for file operations like copy and move. Press `Tab` to switch the active panel. Press `h` / `l` to activate the left / right panel directly.
|
|
76
|
+
|
|
77
|
+
On startup the active panel opens in the current working directory. The inactive panel restores the path from the previous session.
|
|
78
|
+
|
|
79
|
+
### Navigation
|
|
80
|
+
|
|
81
|
+
| Key | Action |
|
|
82
|
+
| -------------------- | -------------------------------------- |
|
|
83
|
+
| `Up` / `k` | Move cursor up |
|
|
84
|
+
| `Down` / `j` | Move cursor down |
|
|
85
|
+
| `Enter` | Enter directory or open VFS |
|
|
86
|
+
| `Backspace` | Go to parent directory (or exit VFS) |
|
|
87
|
+
| `Left` / `Right` | Page up / page down |
|
|
88
|
+
| `PgUp` / `PgDn` | Page up / page down |
|
|
89
|
+
| `Home` / `^` | Jump to first file |
|
|
90
|
+
| `End` / `G` | Jump to last file |
|
|
91
|
+
| `Ctrl-D` / `Ctrl-U` | Half-page down / up |
|
|
92
|
+
| `Ctrl-L` | Reload current directory |
|
|
93
|
+
| `Tab` | Switch active panel |
|
|
94
|
+
| `h` / `l` | Activate left / right panel |
|
|
95
|
+
| `q` | Quit |
|
|
96
|
+
|
|
97
|
+
### File operations (`x`)
|
|
98
|
+
|
|
99
|
+
Press `x` to open the **command** menu:
|
|
100
|
+
|
|
101
|
+
| Key | Action |
|
|
102
|
+
| --- | ------------------------------------------------------------------- |
|
|
103
|
+
| `c` | **Copy** -- copy file or tagged files from active to inactive panel |
|
|
104
|
+
| `m` | **Move** -- move (rename) file or tagged files |
|
|
105
|
+
| `d` | **Delete** -- delete file or tagged files |
|
|
106
|
+
| `k` | **Mkdir** -- create a new directory |
|
|
107
|
+
| `t` | **Touch** -- create an empty file |
|
|
108
|
+
| `p` | **Chmod** -- change file permissions |
|
|
109
|
+
| `r` | **Rename** -- rename the selected file |
|
|
110
|
+
| `g` | **Chdir** -- type a path to navigate to |
|
|
111
|
+
|
|
112
|
+
Copy and move operations use the inactive panel's current path as the default destination. A prompt lets you edit the destination before confirming.
|
|
113
|
+
|
|
114
|
+
### Tagging and group operations
|
|
115
|
+
|
|
116
|
+
Press `Space` on a file to **tag** it (marked with `+`). Tagged files are used as the source for copy, move, delete, and chmod. If nothing is tagged, the operation applies to the file under the cursor.
|
|
117
|
+
|
|
118
|
+
| Key | Action |
|
|
119
|
+
| ------- | --------------------------------------------------- |
|
|
120
|
+
| `Space` | Toggle tag on current file and move down |
|
|
121
|
+
| `+` | Tag all files in current directory |
|
|
122
|
+
| `_` | Untag all |
|
|
123
|
+
| `i` | Calculate sizes of tagged (or selected) directories |
|
|
124
|
+
|
|
125
|
+
### Bookmarks (`b`)
|
|
126
|
+
|
|
127
|
+
Press `b` to open the **bookmark** menu for quick jumps to common directories (home, desktop, downloads, etc.).
|
|
128
|
+
|
|
129
|
+
### Remotes (`r`)
|
|
130
|
+
|
|
131
|
+
Press `r` to open the **remote** menu. This scans `~/.xc/remotes/` for VFS config files (`.s3`, `.gcs`, `.oci`, `.gdrive`, `.ssh`) and presents them as a selector. Choosing a remote opens it on the active panel, just like pressing `Enter` on a VFS config file.
|
|
132
|
+
|
|
133
|
+
This lets you keep all your remote connections in one place and access them from any directory without navigating to where the config files live.
|
|
134
|
+
|
|
135
|
+
Example setup:
|
|
136
|
+
|
|
137
|
+
```text
|
|
138
|
+
~/.xc/remotes/
|
|
139
|
+
production.s3
|
|
140
|
+
analytics.gcs
|
|
141
|
+
storage.oci
|
|
142
|
+
shared-drive.gdrive
|
|
143
|
+
webserver.ssh
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Editor (`e`) and view (`v`)
|
|
147
|
+
|
|
148
|
+
Press `e` to open a file in an editor, or `v` to view it. These menus launch external commands with the current file path substituted via macros (see below). On remote VFS (SSH, S3, GCS, OCI, GDrive), the file is automatically downloaded to a temp location, opened locally, and uploaded back if modified.
|
|
149
|
+
|
|
150
|
+
### Running shell commands
|
|
151
|
+
|
|
152
|
+
There are two command-line modes:
|
|
153
|
+
|
|
154
|
+
| Key | Mode | Behavior |
|
|
155
|
+
| --- | ---------- | ---------------------------------------------- |
|
|
156
|
+
| `;` | **Direct** | Run a command interactively in the terminal |
|
|
157
|
+
| `:` | **Piped** | Run a command; output is piped through `less` |
|
|
158
|
+
|
|
159
|
+
Both modes support macro expansion. The command runs in the active panel's current directory. If the command exits with a non-zero code, the error is shown in the bottom line.
|
|
160
|
+
|
|
161
|
+
### Alternate screen and command output
|
|
162
|
+
|
|
163
|
+
xc runs on the terminal's **alternate screen** -- the panels never mix with your shell's scroll buffer. When you run a shell command (`;` or `:`), xc temporarily switches back to the **main screen** so the command's output is preserved in the normal scroll buffer.
|
|
164
|
+
|
|
165
|
+
To review previous command output without running anything:
|
|
166
|
+
|
|
167
|
+
| Key | Action |
|
|
168
|
+
| ---------------- | ------------------------------- |
|
|
169
|
+
| `Esc` `Esc` | Switch to main screen |
|
|
170
|
+
| `Ctrl-O` | Switch to main screen |
|
|
171
|
+
| `Esc` or `Ctrl-O` | Return to panels (main screen) |
|
|
172
|
+
|
|
173
|
+
Once on the main screen you can scroll through your terminal's history as usual. Press `Esc` or `Ctrl-O` to return to the file panels.
|
|
174
|
+
|
|
175
|
+
### Search (`/`)
|
|
176
|
+
|
|
177
|
+
Press `/` to start an incremental search. Type characters to filter -- the cursor jumps to the first matching file. Press `Enter` to accept or `Esc` to cancel.
|
|
178
|
+
|
|
179
|
+
### Grep (`s` / `S`)
|
|
180
|
+
|
|
181
|
+
Press `s` for case-sensitive search or `S` for case-insensitive search. This is a two-step prompt:
|
|
182
|
+
|
|
183
|
+
1. **File pattern** -- `grep in ` (or `igrep in `) appears. Enter a glob pattern to filter by filename. Leave empty to search all files.
|
|
184
|
+
2. **Search string** -- `grep in *.py for ` appears. Enter the text to find. Leave empty to list files matching the pattern only.
|
|
185
|
+
|
|
186
|
+
The file pattern uses Unix shell-style wildcards (matched against the filename only, not the path):
|
|
187
|
+
|
|
188
|
+
| Pattern | Meaning | Example |
|
|
189
|
+
| --------- | ------------------------------------------------ | -------------------- |
|
|
190
|
+
| `*` | Matches everything | `*.py` -- all Python |
|
|
191
|
+
| `?` | Matches any single character | `?.txt` -- `a.txt` |
|
|
192
|
+
| `[seq]` | Matches any character in *seq* | `[abc].py` |
|
|
193
|
+
| `[!seq]` | Matches any character **not** in *seq* | `[!.]cfg` |
|
|
194
|
+
|
|
195
|
+
Patterns do **not** support `**` (recursive globs) or path separators -- they match the base filename only.
|
|
196
|
+
|
|
197
|
+
Search is implemented in pure Python -- no external tools required. Binary files are automatically skipped. Hidden directories (starting with `.`) are excluded. A spinner shows progress during search; press `ESC` to cancel.
|
|
198
|
+
|
|
199
|
+
Results are displayed as a virtual filesystem tree (GREP) on the current panel. Navigate the results normally -- directories expand, `..` exits back to the real filesystem.
|
|
200
|
+
|
|
201
|
+
Grep works only on local filesystems.
|
|
202
|
+
|
|
203
|
+
### Command history (`Esc` `h`)
|
|
204
|
+
|
|
205
|
+
In command-line mode (`;` or `:`), press `Esc` then `h` to open a history selector showing previously executed commands. Use `Up` / `Down` to browse, `Enter` to accept, `Esc` to cancel. History is persisted across sessions (up to 100 entries).
|
|
206
|
+
|
|
207
|
+
### Customizing menus
|
|
208
|
+
|
|
209
|
+
xc is a single Python script. Menus and keymaps are defined at the bottom of the file as plain data -- just edit them to add your own editors, bookmarks, or commands:
|
|
210
|
+
|
|
211
|
+
```python
|
|
212
|
+
app.add_menu("editor", [
|
|
213
|
+
MenuItem("v", "vi", lambda: app.action_run("vi %F")),
|
|
214
|
+
MenuItem("c", "code", lambda: app.action_run("code %F")),
|
|
215
|
+
])
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
There is no config file on purpose. The script **is** the config.
|
|
219
|
+
|
|
220
|
+
### Virtual filesystems
|
|
221
|
+
|
|
222
|
+
Entering certain files opens them as virtual directories:
|
|
223
|
+
|
|
224
|
+
| Extension | VFS | Description |
|
|
225
|
+
| -------------------------------------------- | ------ | ----------------------------------- |
|
|
226
|
+
| `.tar`, `.tar.gz`, `.tgz`, `.tar.bz2` | TAR | Browse tar archives |
|
|
227
|
+
| `.zip` | ZIP | Browse zip archives |
|
|
228
|
+
| `.gz`, `.bz2`, `.xz`, `.lzma` | - | View compressed single files |
|
|
229
|
+
| `.s3` | S3 | Browse Amazon S3 buckets |
|
|
230
|
+
| `.gcs` | GCS | Browse Google Cloud Storage buckets |
|
|
231
|
+
| `.oci` | OCI | Browse Oracle Cloud Object Storage |
|
|
232
|
+
| `.gdrive` | GDrive | Browse Google Drive folders |
|
|
233
|
+
| `.ssh` | SSH | Browse remote servers over SSH |
|
|
234
|
+
|
|
235
|
+
VFS config files (`.s3`, `.gcs`, `.oci`, `.gdrive`, `.ssh`) are simple `key=value` text files. The path header shows the VFS type, e.g. `~/servers/prod.ssh (SSH)`.
|
|
236
|
+
|
|
237
|
+
**S3 example** (`production.s3`):
|
|
238
|
+
|
|
239
|
+
```text
|
|
240
|
+
type=s3
|
|
241
|
+
bucket=my-data-bucket
|
|
242
|
+
AWS_PROFILE=production
|
|
243
|
+
AWS_REGION=eu-west-1
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
`AWS_PROFILE` selects a named profile from `~/.aws/credentials`. You can also specify `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` inline, but using a profile is recommended. If all credentials are omitted, the default AWS credential chain is used.
|
|
247
|
+
|
|
248
|
+
**GCS example** (`analytics.gcs`):
|
|
249
|
+
|
|
250
|
+
```text
|
|
251
|
+
type=gcs
|
|
252
|
+
bucket=my-analytics-bucket
|
|
253
|
+
key=service-account.json
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
The `key` path can be relative (resolved from the active panel's current directory), absolute, or use `~`, `$HOME`, or `$(HOME)` to refer to the home directory. If omitted, application default credentials are used.
|
|
257
|
+
|
|
258
|
+
**OCI example** (`storage.oci`):
|
|
259
|
+
|
|
260
|
+
```text
|
|
261
|
+
type=oci
|
|
262
|
+
bucket=my-bucket
|
|
263
|
+
OCI_BUCKET_NAMESPACE=mynamespace
|
|
264
|
+
OCI_USER=ocid1.user.oc1..aaa...
|
|
265
|
+
OCI_FINGERPRINT=aa:bb:cc:...
|
|
266
|
+
OCI_TENANCY=ocid1.tenancy.oc1..aaa...
|
|
267
|
+
OCI_REGION=uk-london-1
|
|
268
|
+
OCI_KEY_FILE=my-key.pem
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
`OCI_KEY_FILE` is a path to a PEM private key file. Alternatively, use `OCI_KEY_BASE64` to embed the key inline as base64. If neither is provided, the default OCI config (`~/.oci/config`) is used.
|
|
272
|
+
|
|
273
|
+
**Google Drive example** (`shared.gdrive`):
|
|
274
|
+
|
|
275
|
+
```text
|
|
276
|
+
type=gdrive
|
|
277
|
+
folder=1Z_NJ0-LAPzaO7eursL92DWPmFKQT3lpK
|
|
278
|
+
key=service-account.json
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
The `folder` is the ID from the Google Drive folder URL. The `key` path points to a service account JSON credentials file (relative paths are resolved from the config file's directory). If omitted, application default credentials are used. Shared drives and folders shared from other accounts are supported.
|
|
282
|
+
|
|
283
|
+
**SSH example** (`prod.ssh`):
|
|
284
|
+
|
|
285
|
+
```text
|
|
286
|
+
kind=ssh
|
|
287
|
+
host=prod-server
|
|
288
|
+
user=deploy
|
|
289
|
+
identity=~/.ssh/id_ed25519
|
|
290
|
+
port=22
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
Only `host` is required. All other fields are optional -- SSH will pick them up from `~/.ssh/config`. The `host` value can be an SSH config alias. The `identity` path supports `~`, `$HOME`, and `$(HOME)` prefixes.
|
|
294
|
+
|
|
295
|
+
### Remote file editing
|
|
296
|
+
|
|
297
|
+
The `%F` macro works transparently on remote VFS (SSH, S3, GCS, OCI, GDrive). When you run a command like `vi %F` on a remote file, xc automatically:
|
|
298
|
+
|
|
299
|
+
1. Downloads the file to a local temp location
|
|
300
|
+
2. Runs the command against the local copy
|
|
301
|
+
3. If the command exits successfully and the file was modified, uploads it back
|
|
302
|
+
|
|
303
|
+
This means editors, viewers, and any shell command work on remote files the same way as local ones.
|
|
304
|
+
|
|
305
|
+
## Macros
|
|
306
|
+
|
|
307
|
+
Editor, view, and shell command modes (`;` and `:`) all support macro expansion. Macros are prefixed with `%` and let you refer to the current file, directory, or tagged selection in your commands.
|
|
308
|
+
|
|
309
|
+
| Macro | Description |
|
|
310
|
+
| ----- | ------------------------------------------------------ |
|
|
311
|
+
| `%f` | Current file name |
|
|
312
|
+
| `%F` | Current file full path |
|
|
313
|
+
| `%x` | Current file name without extension |
|
|
314
|
+
| `%X` | Current file full path without extension |
|
|
315
|
+
| `%d` | Current directory name |
|
|
316
|
+
| `%D` | Current directory full path |
|
|
317
|
+
| `%m` | Tagged file names (space-separated, shell-quoted) |
|
|
318
|
+
| `%M` | Tagged file full paths (space-separated, shell-quoted) |
|
|
319
|
+
| `%&` | Run command in the background (no terminal output) |
|
|
320
|
+
|
|
321
|
+
### Quoting
|
|
322
|
+
|
|
323
|
+
All macros are automatically shell-quoted. To disable quoting, prefix the macro letter with `~`:
|
|
324
|
+
|
|
325
|
+
| Macro | Description |
|
|
326
|
+
| ------ | --------------------------- |
|
|
327
|
+
| `%~f` | File name, unquoted |
|
|
328
|
+
| `%~F` | File path, unquoted |
|
|
329
|
+
| `%~m` | Tagged file names, unquoted |
|
|
330
|
+
| `%~M` | Tagged file paths, unquoted |
|
|
331
|
+
|
|
332
|
+
### Examples
|
|
333
|
+
|
|
334
|
+
```text
|
|
335
|
+
vi %F → vi '/home/user/hello world.txt'
|
|
336
|
+
less %F → less '/home/user/notes.md'
|
|
337
|
+
tar czf %x.tar.gz %~f → tar czf 'mydir.tar.gz' mydir
|
|
338
|
+
echo %m → echo 'file1.txt' 'file2.txt'
|
|
339
|
+
cp %M /tmp %& → cp '/home/user/a.txt' '/home/user/b.txt' /tmp (background)
|
|
340
|
+
```
|
xcfm-0.1.0/README.md
ADDED
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
# xc
|
|
2
|
+
|
|
3
|
+
A two-panel console file manager inspired by Midnight Commander, written in Python.
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
## Intro
|
|
8
|
+
|
|
9
|
+
The Python version of xc is a single self-contained script (`xc.py`) that runs via [uv](https://docs.astral.sh/uv/). This means:
|
|
10
|
+
|
|
11
|
+
- **Zero setup** -- no virtualenv, no `pip install`, no `requirements.txt`. Just run `uv run xc.py`.
|
|
12
|
+
- **Inline dependencies** -- the script header declares its own dependencies (`boto3`, `google-cloud-storage`, `oci`, `google-api-python-client`), and uv resolves and caches them automatically on the first run.
|
|
13
|
+
- **Reproducible** -- uv pins the Python version (`>=3.11`) and handles isolation, so the script works the same way on any machine.
|
|
14
|
+
- **Single file to deploy** -- copy `xc.py` to a server, a dotfiles repo, or a USB stick. There is nothing else to carry.
|
|
15
|
+
|
|
16
|
+
### Installing uv
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
# macOS / Linux
|
|
20
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
21
|
+
|
|
22
|
+
# Homebrew
|
|
23
|
+
brew install uv
|
|
24
|
+
|
|
25
|
+
# Windows
|
|
26
|
+
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
After installing, run the file manager with:
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
uv run xc.py
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The script has a shebang line, so you can rename it, make it executable, and put it on your PATH:
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
cp xc.py ~/.local/bin/xc
|
|
39
|
+
chmod +x ~/.local/bin/xc
|
|
40
|
+
xc
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Development
|
|
44
|
+
|
|
45
|
+
The `pyproject.toml` in the repository is only used for local development tooling (e.g. `black` formatter settings). It is **not** needed to run xc -- `xc.py` is fully self-contained with its own inline dependency declarations.
|
|
46
|
+
|
|
47
|
+
### Self-update
|
|
48
|
+
|
|
49
|
+
To update xc to the latest version from GitHub:
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
xc -u
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
This fetches the latest `xc.py` from the repository, compares versions, and replaces the current binary if a newer version is available. The previous version is saved as `xc.prev` next to the executable.
|
|
56
|
+
|
|
57
|
+
## User manual
|
|
58
|
+
|
|
59
|
+
### Dual-panel concept
|
|
60
|
+
|
|
61
|
+
xc shows two file panels side by side. One panel is **active** (highlighted border), the other is **inactive**. You navigate files in the active panel and use the inactive panel as a target for file operations like copy and move. Press `Tab` to switch the active panel. Press `h` / `l` to activate the left / right panel directly.
|
|
62
|
+
|
|
63
|
+
On startup the active panel opens in the current working directory. The inactive panel restores the path from the previous session.
|
|
64
|
+
|
|
65
|
+
### Navigation
|
|
66
|
+
|
|
67
|
+
| Key | Action |
|
|
68
|
+
| -------------------- | -------------------------------------- |
|
|
69
|
+
| `Up` / `k` | Move cursor up |
|
|
70
|
+
| `Down` / `j` | Move cursor down |
|
|
71
|
+
| `Enter` | Enter directory or open VFS |
|
|
72
|
+
| `Backspace` | Go to parent directory (or exit VFS) |
|
|
73
|
+
| `Left` / `Right` | Page up / page down |
|
|
74
|
+
| `PgUp` / `PgDn` | Page up / page down |
|
|
75
|
+
| `Home` / `^` | Jump to first file |
|
|
76
|
+
| `End` / `G` | Jump to last file |
|
|
77
|
+
| `Ctrl-D` / `Ctrl-U` | Half-page down / up |
|
|
78
|
+
| `Ctrl-L` | Reload current directory |
|
|
79
|
+
| `Tab` | Switch active panel |
|
|
80
|
+
| `h` / `l` | Activate left / right panel |
|
|
81
|
+
| `q` | Quit |
|
|
82
|
+
|
|
83
|
+
### File operations (`x`)
|
|
84
|
+
|
|
85
|
+
Press `x` to open the **command** menu:
|
|
86
|
+
|
|
87
|
+
| Key | Action |
|
|
88
|
+
| --- | ------------------------------------------------------------------- |
|
|
89
|
+
| `c` | **Copy** -- copy file or tagged files from active to inactive panel |
|
|
90
|
+
| `m` | **Move** -- move (rename) file or tagged files |
|
|
91
|
+
| `d` | **Delete** -- delete file or tagged files |
|
|
92
|
+
| `k` | **Mkdir** -- create a new directory |
|
|
93
|
+
| `t` | **Touch** -- create an empty file |
|
|
94
|
+
| `p` | **Chmod** -- change file permissions |
|
|
95
|
+
| `r` | **Rename** -- rename the selected file |
|
|
96
|
+
| `g` | **Chdir** -- type a path to navigate to |
|
|
97
|
+
|
|
98
|
+
Copy and move operations use the inactive panel's current path as the default destination. A prompt lets you edit the destination before confirming.
|
|
99
|
+
|
|
100
|
+
### Tagging and group operations
|
|
101
|
+
|
|
102
|
+
Press `Space` on a file to **tag** it (marked with `+`). Tagged files are used as the source for copy, move, delete, and chmod. If nothing is tagged, the operation applies to the file under the cursor.
|
|
103
|
+
|
|
104
|
+
| Key | Action |
|
|
105
|
+
| ------- | --------------------------------------------------- |
|
|
106
|
+
| `Space` | Toggle tag on current file and move down |
|
|
107
|
+
| `+` | Tag all files in current directory |
|
|
108
|
+
| `_` | Untag all |
|
|
109
|
+
| `i` | Calculate sizes of tagged (or selected) directories |
|
|
110
|
+
|
|
111
|
+
### Bookmarks (`b`)
|
|
112
|
+
|
|
113
|
+
Press `b` to open the **bookmark** menu for quick jumps to common directories (home, desktop, downloads, etc.).
|
|
114
|
+
|
|
115
|
+
### Remotes (`r`)
|
|
116
|
+
|
|
117
|
+
Press `r` to open the **remote** menu. This scans `~/.xc/remotes/` for VFS config files (`.s3`, `.gcs`, `.oci`, `.gdrive`, `.ssh`) and presents them as a selector. Choosing a remote opens it on the active panel, just like pressing `Enter` on a VFS config file.
|
|
118
|
+
|
|
119
|
+
This lets you keep all your remote connections in one place and access them from any directory without navigating to where the config files live.
|
|
120
|
+
|
|
121
|
+
Example setup:
|
|
122
|
+
|
|
123
|
+
```text
|
|
124
|
+
~/.xc/remotes/
|
|
125
|
+
production.s3
|
|
126
|
+
analytics.gcs
|
|
127
|
+
storage.oci
|
|
128
|
+
shared-drive.gdrive
|
|
129
|
+
webserver.ssh
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Editor (`e`) and view (`v`)
|
|
133
|
+
|
|
134
|
+
Press `e` to open a file in an editor, or `v` to view it. These menus launch external commands with the current file path substituted via macros (see below). On remote VFS (SSH, S3, GCS, OCI, GDrive), the file is automatically downloaded to a temp location, opened locally, and uploaded back if modified.
|
|
135
|
+
|
|
136
|
+
### Running shell commands
|
|
137
|
+
|
|
138
|
+
There are two command-line modes:
|
|
139
|
+
|
|
140
|
+
| Key | Mode | Behavior |
|
|
141
|
+
| --- | ---------- | ---------------------------------------------- |
|
|
142
|
+
| `;` | **Direct** | Run a command interactively in the terminal |
|
|
143
|
+
| `:` | **Piped** | Run a command; output is piped through `less` |
|
|
144
|
+
|
|
145
|
+
Both modes support macro expansion. The command runs in the active panel's current directory. If the command exits with a non-zero code, the error is shown in the bottom line.
|
|
146
|
+
|
|
147
|
+
### Alternate screen and command output
|
|
148
|
+
|
|
149
|
+
xc runs on the terminal's **alternate screen** -- the panels never mix with your shell's scroll buffer. When you run a shell command (`;` or `:`), xc temporarily switches back to the **main screen** so the command's output is preserved in the normal scroll buffer.
|
|
150
|
+
|
|
151
|
+
To review previous command output without running anything:
|
|
152
|
+
|
|
153
|
+
| Key | Action |
|
|
154
|
+
| ---------------- | ------------------------------- |
|
|
155
|
+
| `Esc` `Esc` | Switch to main screen |
|
|
156
|
+
| `Ctrl-O` | Switch to main screen |
|
|
157
|
+
| `Esc` or `Ctrl-O` | Return to panels (main screen) |
|
|
158
|
+
|
|
159
|
+
Once on the main screen you can scroll through your terminal's history as usual. Press `Esc` or `Ctrl-O` to return to the file panels.
|
|
160
|
+
|
|
161
|
+
### Search (`/`)
|
|
162
|
+
|
|
163
|
+
Press `/` to start an incremental search. Type characters to filter -- the cursor jumps to the first matching file. Press `Enter` to accept or `Esc` to cancel.
|
|
164
|
+
|
|
165
|
+
### Grep (`s` / `S`)
|
|
166
|
+
|
|
167
|
+
Press `s` for case-sensitive search or `S` for case-insensitive search. This is a two-step prompt:
|
|
168
|
+
|
|
169
|
+
1. **File pattern** -- `grep in ` (or `igrep in `) appears. Enter a glob pattern to filter by filename. Leave empty to search all files.
|
|
170
|
+
2. **Search string** -- `grep in *.py for ` appears. Enter the text to find. Leave empty to list files matching the pattern only.
|
|
171
|
+
|
|
172
|
+
The file pattern uses Unix shell-style wildcards (matched against the filename only, not the path):
|
|
173
|
+
|
|
174
|
+
| Pattern | Meaning | Example |
|
|
175
|
+
| --------- | ------------------------------------------------ | -------------------- |
|
|
176
|
+
| `*` | Matches everything | `*.py` -- all Python |
|
|
177
|
+
| `?` | Matches any single character | `?.txt` -- `a.txt` |
|
|
178
|
+
| `[seq]` | Matches any character in *seq* | `[abc].py` |
|
|
179
|
+
| `[!seq]` | Matches any character **not** in *seq* | `[!.]cfg` |
|
|
180
|
+
|
|
181
|
+
Patterns do **not** support `**` (recursive globs) or path separators -- they match the base filename only.
|
|
182
|
+
|
|
183
|
+
Search is implemented in pure Python -- no external tools required. Binary files are automatically skipped. Hidden directories (starting with `.`) are excluded. A spinner shows progress during search; press `ESC` to cancel.
|
|
184
|
+
|
|
185
|
+
Results are displayed as a virtual filesystem tree (GREP) on the current panel. Navigate the results normally -- directories expand, `..` exits back to the real filesystem.
|
|
186
|
+
|
|
187
|
+
Grep works only on local filesystems.
|
|
188
|
+
|
|
189
|
+
### Command history (`Esc` `h`)
|
|
190
|
+
|
|
191
|
+
In command-line mode (`;` or `:`), press `Esc` then `h` to open a history selector showing previously executed commands. Use `Up` / `Down` to browse, `Enter` to accept, `Esc` to cancel. History is persisted across sessions (up to 100 entries).
|
|
192
|
+
|
|
193
|
+
### Customizing menus
|
|
194
|
+
|
|
195
|
+
xc is a single Python script. Menus and keymaps are defined at the bottom of the file as plain data -- just edit them to add your own editors, bookmarks, or commands:
|
|
196
|
+
|
|
197
|
+
```python
|
|
198
|
+
app.add_menu("editor", [
|
|
199
|
+
MenuItem("v", "vi", lambda: app.action_run("vi %F")),
|
|
200
|
+
MenuItem("c", "code", lambda: app.action_run("code %F")),
|
|
201
|
+
])
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
There is no config file on purpose. The script **is** the config.
|
|
205
|
+
|
|
206
|
+
### Virtual filesystems
|
|
207
|
+
|
|
208
|
+
Entering certain files opens them as virtual directories:
|
|
209
|
+
|
|
210
|
+
| Extension | VFS | Description |
|
|
211
|
+
| -------------------------------------------- | ------ | ----------------------------------- |
|
|
212
|
+
| `.tar`, `.tar.gz`, `.tgz`, `.tar.bz2` | TAR | Browse tar archives |
|
|
213
|
+
| `.zip` | ZIP | Browse zip archives |
|
|
214
|
+
| `.gz`, `.bz2`, `.xz`, `.lzma` | - | View compressed single files |
|
|
215
|
+
| `.s3` | S3 | Browse Amazon S3 buckets |
|
|
216
|
+
| `.gcs` | GCS | Browse Google Cloud Storage buckets |
|
|
217
|
+
| `.oci` | OCI | Browse Oracle Cloud Object Storage |
|
|
218
|
+
| `.gdrive` | GDrive | Browse Google Drive folders |
|
|
219
|
+
| `.ssh` | SSH | Browse remote servers over SSH |
|
|
220
|
+
|
|
221
|
+
VFS config files (`.s3`, `.gcs`, `.oci`, `.gdrive`, `.ssh`) are simple `key=value` text files. The path header shows the VFS type, e.g. `~/servers/prod.ssh (SSH)`.
|
|
222
|
+
|
|
223
|
+
**S3 example** (`production.s3`):
|
|
224
|
+
|
|
225
|
+
```text
|
|
226
|
+
type=s3
|
|
227
|
+
bucket=my-data-bucket
|
|
228
|
+
AWS_PROFILE=production
|
|
229
|
+
AWS_REGION=eu-west-1
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
`AWS_PROFILE` selects a named profile from `~/.aws/credentials`. You can also specify `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` inline, but using a profile is recommended. If all credentials are omitted, the default AWS credential chain is used.
|
|
233
|
+
|
|
234
|
+
**GCS example** (`analytics.gcs`):
|
|
235
|
+
|
|
236
|
+
```text
|
|
237
|
+
type=gcs
|
|
238
|
+
bucket=my-analytics-bucket
|
|
239
|
+
key=service-account.json
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
The `key` path can be relative (resolved from the active panel's current directory), absolute, or use `~`, `$HOME`, or `$(HOME)` to refer to the home directory. If omitted, application default credentials are used.
|
|
243
|
+
|
|
244
|
+
**OCI example** (`storage.oci`):
|
|
245
|
+
|
|
246
|
+
```text
|
|
247
|
+
type=oci
|
|
248
|
+
bucket=my-bucket
|
|
249
|
+
OCI_BUCKET_NAMESPACE=mynamespace
|
|
250
|
+
OCI_USER=ocid1.user.oc1..aaa...
|
|
251
|
+
OCI_FINGERPRINT=aa:bb:cc:...
|
|
252
|
+
OCI_TENANCY=ocid1.tenancy.oc1..aaa...
|
|
253
|
+
OCI_REGION=uk-london-1
|
|
254
|
+
OCI_KEY_FILE=my-key.pem
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
`OCI_KEY_FILE` is a path to a PEM private key file. Alternatively, use `OCI_KEY_BASE64` to embed the key inline as base64. If neither is provided, the default OCI config (`~/.oci/config`) is used.
|
|
258
|
+
|
|
259
|
+
**Google Drive example** (`shared.gdrive`):
|
|
260
|
+
|
|
261
|
+
```text
|
|
262
|
+
type=gdrive
|
|
263
|
+
folder=1Z_NJ0-LAPzaO7eursL92DWPmFKQT3lpK
|
|
264
|
+
key=service-account.json
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
The `folder` is the ID from the Google Drive folder URL. The `key` path points to a service account JSON credentials file (relative paths are resolved from the config file's directory). If omitted, application default credentials are used. Shared drives and folders shared from other accounts are supported.
|
|
268
|
+
|
|
269
|
+
**SSH example** (`prod.ssh`):
|
|
270
|
+
|
|
271
|
+
```text
|
|
272
|
+
kind=ssh
|
|
273
|
+
host=prod-server
|
|
274
|
+
user=deploy
|
|
275
|
+
identity=~/.ssh/id_ed25519
|
|
276
|
+
port=22
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Only `host` is required. All other fields are optional -- SSH will pick them up from `~/.ssh/config`. The `host` value can be an SSH config alias. The `identity` path supports `~`, `$HOME`, and `$(HOME)` prefixes.
|
|
280
|
+
|
|
281
|
+
### Remote file editing
|
|
282
|
+
|
|
283
|
+
The `%F` macro works transparently on remote VFS (SSH, S3, GCS, OCI, GDrive). When you run a command like `vi %F` on a remote file, xc automatically:
|
|
284
|
+
|
|
285
|
+
1. Downloads the file to a local temp location
|
|
286
|
+
2. Runs the command against the local copy
|
|
287
|
+
3. If the command exits successfully and the file was modified, uploads it back
|
|
288
|
+
|
|
289
|
+
This means editors, viewers, and any shell command work on remote files the same way as local ones.
|
|
290
|
+
|
|
291
|
+
## Macros
|
|
292
|
+
|
|
293
|
+
Editor, view, and shell command modes (`;` and `:`) all support macro expansion. Macros are prefixed with `%` and let you refer to the current file, directory, or tagged selection in your commands.
|
|
294
|
+
|
|
295
|
+
| Macro | Description |
|
|
296
|
+
| ----- | ------------------------------------------------------ |
|
|
297
|
+
| `%f` | Current file name |
|
|
298
|
+
| `%F` | Current file full path |
|
|
299
|
+
| `%x` | Current file name without extension |
|
|
300
|
+
| `%X` | Current file full path without extension |
|
|
301
|
+
| `%d` | Current directory name |
|
|
302
|
+
| `%D` | Current directory full path |
|
|
303
|
+
| `%m` | Tagged file names (space-separated, shell-quoted) |
|
|
304
|
+
| `%M` | Tagged file full paths (space-separated, shell-quoted) |
|
|
305
|
+
| `%&` | Run command in the background (no terminal output) |
|
|
306
|
+
|
|
307
|
+
### Quoting
|
|
308
|
+
|
|
309
|
+
All macros are automatically shell-quoted. To disable quoting, prefix the macro letter with `~`:
|
|
310
|
+
|
|
311
|
+
| Macro | Description |
|
|
312
|
+
| ------ | --------------------------- |
|
|
313
|
+
| `%~f` | File name, unquoted |
|
|
314
|
+
| `%~F` | File path, unquoted |
|
|
315
|
+
| `%~m` | Tagged file names, unquoted |
|
|
316
|
+
| `%~M` | Tagged file paths, unquoted |
|
|
317
|
+
|
|
318
|
+
### Examples
|
|
319
|
+
|
|
320
|
+
```text
|
|
321
|
+
vi %F → vi '/home/user/hello world.txt'
|
|
322
|
+
less %F → less '/home/user/notes.md'
|
|
323
|
+
tar czf %x.tar.gz %~f → tar czf 'mydir.tar.gz' mydir
|
|
324
|
+
echo %m → echo 'file1.txt' 'file2.txt'
|
|
325
|
+
cp %M /tmp %& → cp '/home/user/a.txt' '/home/user/b.txt' /tmp (background)
|
|
326
|
+
```
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "xcfm"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Two-panel console file manager"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.11"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"boto3",
|
|
9
|
+
"google-cloud-storage",
|
|
10
|
+
"oci",
|
|
11
|
+
"google-api-python-client",
|
|
12
|
+
"google-auth",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
[project.scripts]
|
|
16
|
+
xcfm = "xc:entry"
|
|
17
|
+
|
|
18
|
+
[tool.black]
|
|
19
|
+
line-length = 80
|
|
20
|
+
target-version = ["py311"]
|
|
21
|
+
skip-string-normalization = true
|
|
22
|
+
|
xcfm-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: xcfm
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Two-panel console file manager
|
|
5
|
+
Requires-Python: >=3.11
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Dist: boto3
|
|
9
|
+
Requires-Dist: google-cloud-storage
|
|
10
|
+
Requires-Dist: oci
|
|
11
|
+
Requires-Dist: google-api-python-client
|
|
12
|
+
Requires-Dist: google-auth
|
|
13
|
+
Dynamic: license-file
|
|
14
|
+
|
|
15
|
+
# xc
|
|
16
|
+
|
|
17
|
+
A two-panel console file manager inspired by Midnight Commander, written in Python.
|
|
18
|
+
|
|
19
|
+

|
|
20
|
+
|
|
21
|
+
## Intro
|
|
22
|
+
|
|
23
|
+
The Python version of xc is a single self-contained script (`xc.py`) that runs via [uv](https://docs.astral.sh/uv/). This means:
|
|
24
|
+
|
|
25
|
+
- **Zero setup** -- no virtualenv, no `pip install`, no `requirements.txt`. Just run `uv run xc.py`.
|
|
26
|
+
- **Inline dependencies** -- the script header declares its own dependencies (`boto3`, `google-cloud-storage`, `oci`, `google-api-python-client`), and uv resolves and caches them automatically on the first run.
|
|
27
|
+
- **Reproducible** -- uv pins the Python version (`>=3.11`) and handles isolation, so the script works the same way on any machine.
|
|
28
|
+
- **Single file to deploy** -- copy `xc.py` to a server, a dotfiles repo, or a USB stick. There is nothing else to carry.
|
|
29
|
+
|
|
30
|
+
### Installing uv
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
# macOS / Linux
|
|
34
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
35
|
+
|
|
36
|
+
# Homebrew
|
|
37
|
+
brew install uv
|
|
38
|
+
|
|
39
|
+
# Windows
|
|
40
|
+
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
After installing, run the file manager with:
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
uv run xc.py
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The script has a shebang line, so you can rename it, make it executable, and put it on your PATH:
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
cp xc.py ~/.local/bin/xc
|
|
53
|
+
chmod +x ~/.local/bin/xc
|
|
54
|
+
xc
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Development
|
|
58
|
+
|
|
59
|
+
The `pyproject.toml` in the repository is only used for local development tooling (e.g. `black` formatter settings). It is **not** needed to run xc -- `xc.py` is fully self-contained with its own inline dependency declarations.
|
|
60
|
+
|
|
61
|
+
### Self-update
|
|
62
|
+
|
|
63
|
+
To update xc to the latest version from GitHub:
|
|
64
|
+
|
|
65
|
+
```sh
|
|
66
|
+
xc -u
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
This fetches the latest `xc.py` from the repository, compares versions, and replaces the current binary if a newer version is available. The previous version is saved as `xc.prev` next to the executable.
|
|
70
|
+
|
|
71
|
+
## User manual
|
|
72
|
+
|
|
73
|
+
### Dual-panel concept
|
|
74
|
+
|
|
75
|
+
xc shows two file panels side by side. One panel is **active** (highlighted border), the other is **inactive**. You navigate files in the active panel and use the inactive panel as a target for file operations like copy and move. Press `Tab` to switch the active panel. Press `h` / `l` to activate the left / right panel directly.
|
|
76
|
+
|
|
77
|
+
On startup the active panel opens in the current working directory. The inactive panel restores the path from the previous session.
|
|
78
|
+
|
|
79
|
+
### Navigation
|
|
80
|
+
|
|
81
|
+
| Key | Action |
|
|
82
|
+
| -------------------- | -------------------------------------- |
|
|
83
|
+
| `Up` / `k` | Move cursor up |
|
|
84
|
+
| `Down` / `j` | Move cursor down |
|
|
85
|
+
| `Enter` | Enter directory or open VFS |
|
|
86
|
+
| `Backspace` | Go to parent directory (or exit VFS) |
|
|
87
|
+
| `Left` / `Right` | Page up / page down |
|
|
88
|
+
| `PgUp` / `PgDn` | Page up / page down |
|
|
89
|
+
| `Home` / `^` | Jump to first file |
|
|
90
|
+
| `End` / `G` | Jump to last file |
|
|
91
|
+
| `Ctrl-D` / `Ctrl-U` | Half-page down / up |
|
|
92
|
+
| `Ctrl-L` | Reload current directory |
|
|
93
|
+
| `Tab` | Switch active panel |
|
|
94
|
+
| `h` / `l` | Activate left / right panel |
|
|
95
|
+
| `q` | Quit |
|
|
96
|
+
|
|
97
|
+
### File operations (`x`)
|
|
98
|
+
|
|
99
|
+
Press `x` to open the **command** menu:
|
|
100
|
+
|
|
101
|
+
| Key | Action |
|
|
102
|
+
| --- | ------------------------------------------------------------------- |
|
|
103
|
+
| `c` | **Copy** -- copy file or tagged files from active to inactive panel |
|
|
104
|
+
| `m` | **Move** -- move (rename) file or tagged files |
|
|
105
|
+
| `d` | **Delete** -- delete file or tagged files |
|
|
106
|
+
| `k` | **Mkdir** -- create a new directory |
|
|
107
|
+
| `t` | **Touch** -- create an empty file |
|
|
108
|
+
| `p` | **Chmod** -- change file permissions |
|
|
109
|
+
| `r` | **Rename** -- rename the selected file |
|
|
110
|
+
| `g` | **Chdir** -- type a path to navigate to |
|
|
111
|
+
|
|
112
|
+
Copy and move operations use the inactive panel's current path as the default destination. A prompt lets you edit the destination before confirming.
|
|
113
|
+
|
|
114
|
+
### Tagging and group operations
|
|
115
|
+
|
|
116
|
+
Press `Space` on a file to **tag** it (marked with `+`). Tagged files are used as the source for copy, move, delete, and chmod. If nothing is tagged, the operation applies to the file under the cursor.
|
|
117
|
+
|
|
118
|
+
| Key | Action |
|
|
119
|
+
| ------- | --------------------------------------------------- |
|
|
120
|
+
| `Space` | Toggle tag on current file and move down |
|
|
121
|
+
| `+` | Tag all files in current directory |
|
|
122
|
+
| `_` | Untag all |
|
|
123
|
+
| `i` | Calculate sizes of tagged (or selected) directories |
|
|
124
|
+
|
|
125
|
+
### Bookmarks (`b`)
|
|
126
|
+
|
|
127
|
+
Press `b` to open the **bookmark** menu for quick jumps to common directories (home, desktop, downloads, etc.).
|
|
128
|
+
|
|
129
|
+
### Remotes (`r`)
|
|
130
|
+
|
|
131
|
+
Press `r` to open the **remote** menu. This scans `~/.xc/remotes/` for VFS config files (`.s3`, `.gcs`, `.oci`, `.gdrive`, `.ssh`) and presents them as a selector. Choosing a remote opens it on the active panel, just like pressing `Enter` on a VFS config file.
|
|
132
|
+
|
|
133
|
+
This lets you keep all your remote connections in one place and access them from any directory without navigating to where the config files live.
|
|
134
|
+
|
|
135
|
+
Example setup:
|
|
136
|
+
|
|
137
|
+
```text
|
|
138
|
+
~/.xc/remotes/
|
|
139
|
+
production.s3
|
|
140
|
+
analytics.gcs
|
|
141
|
+
storage.oci
|
|
142
|
+
shared-drive.gdrive
|
|
143
|
+
webserver.ssh
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Editor (`e`) and view (`v`)
|
|
147
|
+
|
|
148
|
+
Press `e` to open a file in an editor, or `v` to view it. These menus launch external commands with the current file path substituted via macros (see below). On remote VFS (SSH, S3, GCS, OCI, GDrive), the file is automatically downloaded to a temp location, opened locally, and uploaded back if modified.
|
|
149
|
+
|
|
150
|
+
### Running shell commands
|
|
151
|
+
|
|
152
|
+
There are two command-line modes:
|
|
153
|
+
|
|
154
|
+
| Key | Mode | Behavior |
|
|
155
|
+
| --- | ---------- | ---------------------------------------------- |
|
|
156
|
+
| `;` | **Direct** | Run a command interactively in the terminal |
|
|
157
|
+
| `:` | **Piped** | Run a command; output is piped through `less` |
|
|
158
|
+
|
|
159
|
+
Both modes support macro expansion. The command runs in the active panel's current directory. If the command exits with a non-zero code, the error is shown in the bottom line.
|
|
160
|
+
|
|
161
|
+
### Alternate screen and command output
|
|
162
|
+
|
|
163
|
+
xc runs on the terminal's **alternate screen** -- the panels never mix with your shell's scroll buffer. When you run a shell command (`;` or `:`), xc temporarily switches back to the **main screen** so the command's output is preserved in the normal scroll buffer.
|
|
164
|
+
|
|
165
|
+
To review previous command output without running anything:
|
|
166
|
+
|
|
167
|
+
| Key | Action |
|
|
168
|
+
| ---------------- | ------------------------------- |
|
|
169
|
+
| `Esc` `Esc` | Switch to main screen |
|
|
170
|
+
| `Ctrl-O` | Switch to main screen |
|
|
171
|
+
| `Esc` or `Ctrl-O` | Return to panels (main screen) |
|
|
172
|
+
|
|
173
|
+
Once on the main screen you can scroll through your terminal's history as usual. Press `Esc` or `Ctrl-O` to return to the file panels.
|
|
174
|
+
|
|
175
|
+
### Search (`/`)
|
|
176
|
+
|
|
177
|
+
Press `/` to start an incremental search. Type characters to filter -- the cursor jumps to the first matching file. Press `Enter` to accept or `Esc` to cancel.
|
|
178
|
+
|
|
179
|
+
### Grep (`s` / `S`)
|
|
180
|
+
|
|
181
|
+
Press `s` for case-sensitive search or `S` for case-insensitive search. This is a two-step prompt:
|
|
182
|
+
|
|
183
|
+
1. **File pattern** -- `grep in ` (or `igrep in `) appears. Enter a glob pattern to filter by filename. Leave empty to search all files.
|
|
184
|
+
2. **Search string** -- `grep in *.py for ` appears. Enter the text to find. Leave empty to list files matching the pattern only.
|
|
185
|
+
|
|
186
|
+
The file pattern uses Unix shell-style wildcards (matched against the filename only, not the path):
|
|
187
|
+
|
|
188
|
+
| Pattern | Meaning | Example |
|
|
189
|
+
| --------- | ------------------------------------------------ | -------------------- |
|
|
190
|
+
| `*` | Matches everything | `*.py` -- all Python |
|
|
191
|
+
| `?` | Matches any single character | `?.txt` -- `a.txt` |
|
|
192
|
+
| `[seq]` | Matches any character in *seq* | `[abc].py` |
|
|
193
|
+
| `[!seq]` | Matches any character **not** in *seq* | `[!.]cfg` |
|
|
194
|
+
|
|
195
|
+
Patterns do **not** support `**` (recursive globs) or path separators -- they match the base filename only.
|
|
196
|
+
|
|
197
|
+
Search is implemented in pure Python -- no external tools required. Binary files are automatically skipped. Hidden directories (starting with `.`) are excluded. A spinner shows progress during search; press `ESC` to cancel.
|
|
198
|
+
|
|
199
|
+
Results are displayed as a virtual filesystem tree (GREP) on the current panel. Navigate the results normally -- directories expand, `..` exits back to the real filesystem.
|
|
200
|
+
|
|
201
|
+
Grep works only on local filesystems.
|
|
202
|
+
|
|
203
|
+
### Command history (`Esc` `h`)
|
|
204
|
+
|
|
205
|
+
In command-line mode (`;` or `:`), press `Esc` then `h` to open a history selector showing previously executed commands. Use `Up` / `Down` to browse, `Enter` to accept, `Esc` to cancel. History is persisted across sessions (up to 100 entries).
|
|
206
|
+
|
|
207
|
+
### Customizing menus
|
|
208
|
+
|
|
209
|
+
xc is a single Python script. Menus and keymaps are defined at the bottom of the file as plain data -- just edit them to add your own editors, bookmarks, or commands:
|
|
210
|
+
|
|
211
|
+
```python
|
|
212
|
+
app.add_menu("editor", [
|
|
213
|
+
MenuItem("v", "vi", lambda: app.action_run("vi %F")),
|
|
214
|
+
MenuItem("c", "code", lambda: app.action_run("code %F")),
|
|
215
|
+
])
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
There is no config file on purpose. The script **is** the config.
|
|
219
|
+
|
|
220
|
+
### Virtual filesystems
|
|
221
|
+
|
|
222
|
+
Entering certain files opens them as virtual directories:
|
|
223
|
+
|
|
224
|
+
| Extension | VFS | Description |
|
|
225
|
+
| -------------------------------------------- | ------ | ----------------------------------- |
|
|
226
|
+
| `.tar`, `.tar.gz`, `.tgz`, `.tar.bz2` | TAR | Browse tar archives |
|
|
227
|
+
| `.zip` | ZIP | Browse zip archives |
|
|
228
|
+
| `.gz`, `.bz2`, `.xz`, `.lzma` | - | View compressed single files |
|
|
229
|
+
| `.s3` | S3 | Browse Amazon S3 buckets |
|
|
230
|
+
| `.gcs` | GCS | Browse Google Cloud Storage buckets |
|
|
231
|
+
| `.oci` | OCI | Browse Oracle Cloud Object Storage |
|
|
232
|
+
| `.gdrive` | GDrive | Browse Google Drive folders |
|
|
233
|
+
| `.ssh` | SSH | Browse remote servers over SSH |
|
|
234
|
+
|
|
235
|
+
VFS config files (`.s3`, `.gcs`, `.oci`, `.gdrive`, `.ssh`) are simple `key=value` text files. The path header shows the VFS type, e.g. `~/servers/prod.ssh (SSH)`.
|
|
236
|
+
|
|
237
|
+
**S3 example** (`production.s3`):
|
|
238
|
+
|
|
239
|
+
```text
|
|
240
|
+
type=s3
|
|
241
|
+
bucket=my-data-bucket
|
|
242
|
+
AWS_PROFILE=production
|
|
243
|
+
AWS_REGION=eu-west-1
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
`AWS_PROFILE` selects a named profile from `~/.aws/credentials`. You can also specify `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` inline, but using a profile is recommended. If all credentials are omitted, the default AWS credential chain is used.
|
|
247
|
+
|
|
248
|
+
**GCS example** (`analytics.gcs`):
|
|
249
|
+
|
|
250
|
+
```text
|
|
251
|
+
type=gcs
|
|
252
|
+
bucket=my-analytics-bucket
|
|
253
|
+
key=service-account.json
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
The `key` path can be relative (resolved from the active panel's current directory), absolute, or use `~`, `$HOME`, or `$(HOME)` to refer to the home directory. If omitted, application default credentials are used.
|
|
257
|
+
|
|
258
|
+
**OCI example** (`storage.oci`):
|
|
259
|
+
|
|
260
|
+
```text
|
|
261
|
+
type=oci
|
|
262
|
+
bucket=my-bucket
|
|
263
|
+
OCI_BUCKET_NAMESPACE=mynamespace
|
|
264
|
+
OCI_USER=ocid1.user.oc1..aaa...
|
|
265
|
+
OCI_FINGERPRINT=aa:bb:cc:...
|
|
266
|
+
OCI_TENANCY=ocid1.tenancy.oc1..aaa...
|
|
267
|
+
OCI_REGION=uk-london-1
|
|
268
|
+
OCI_KEY_FILE=my-key.pem
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
`OCI_KEY_FILE` is a path to a PEM private key file. Alternatively, use `OCI_KEY_BASE64` to embed the key inline as base64. If neither is provided, the default OCI config (`~/.oci/config`) is used.
|
|
272
|
+
|
|
273
|
+
**Google Drive example** (`shared.gdrive`):
|
|
274
|
+
|
|
275
|
+
```text
|
|
276
|
+
type=gdrive
|
|
277
|
+
folder=1Z_NJ0-LAPzaO7eursL92DWPmFKQT3lpK
|
|
278
|
+
key=service-account.json
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
The `folder` is the ID from the Google Drive folder URL. The `key` path points to a service account JSON credentials file (relative paths are resolved from the config file's directory). If omitted, application default credentials are used. Shared drives and folders shared from other accounts are supported.
|
|
282
|
+
|
|
283
|
+
**SSH example** (`prod.ssh`):
|
|
284
|
+
|
|
285
|
+
```text
|
|
286
|
+
kind=ssh
|
|
287
|
+
host=prod-server
|
|
288
|
+
user=deploy
|
|
289
|
+
identity=~/.ssh/id_ed25519
|
|
290
|
+
port=22
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
Only `host` is required. All other fields are optional -- SSH will pick them up from `~/.ssh/config`. The `host` value can be an SSH config alias. The `identity` path supports `~`, `$HOME`, and `$(HOME)` prefixes.
|
|
294
|
+
|
|
295
|
+
### Remote file editing
|
|
296
|
+
|
|
297
|
+
The `%F` macro works transparently on remote VFS (SSH, S3, GCS, OCI, GDrive). When you run a command like `vi %F` on a remote file, xc automatically:
|
|
298
|
+
|
|
299
|
+
1. Downloads the file to a local temp location
|
|
300
|
+
2. Runs the command against the local copy
|
|
301
|
+
3. If the command exits successfully and the file was modified, uploads it back
|
|
302
|
+
|
|
303
|
+
This means editors, viewers, and any shell command work on remote files the same way as local ones.
|
|
304
|
+
|
|
305
|
+
## Macros
|
|
306
|
+
|
|
307
|
+
Editor, view, and shell command modes (`;` and `:`) all support macro expansion. Macros are prefixed with `%` and let you refer to the current file, directory, or tagged selection in your commands.
|
|
308
|
+
|
|
309
|
+
| Macro | Description |
|
|
310
|
+
| ----- | ------------------------------------------------------ |
|
|
311
|
+
| `%f` | Current file name |
|
|
312
|
+
| `%F` | Current file full path |
|
|
313
|
+
| `%x` | Current file name without extension |
|
|
314
|
+
| `%X` | Current file full path without extension |
|
|
315
|
+
| `%d` | Current directory name |
|
|
316
|
+
| `%D` | Current directory full path |
|
|
317
|
+
| `%m` | Tagged file names (space-separated, shell-quoted) |
|
|
318
|
+
| `%M` | Tagged file full paths (space-separated, shell-quoted) |
|
|
319
|
+
| `%&` | Run command in the background (no terminal output) |
|
|
320
|
+
|
|
321
|
+
### Quoting
|
|
322
|
+
|
|
323
|
+
All macros are automatically shell-quoted. To disable quoting, prefix the macro letter with `~`:
|
|
324
|
+
|
|
325
|
+
| Macro | Description |
|
|
326
|
+
| ------ | --------------------------- |
|
|
327
|
+
| `%~f` | File name, unquoted |
|
|
328
|
+
| `%~F` | File path, unquoted |
|
|
329
|
+
| `%~m` | Tagged file names, unquoted |
|
|
330
|
+
| `%~M` | Tagged file paths, unquoted |
|
|
331
|
+
|
|
332
|
+
### Examples
|
|
333
|
+
|
|
334
|
+
```text
|
|
335
|
+
vi %F → vi '/home/user/hello world.txt'
|
|
336
|
+
less %F → less '/home/user/notes.md'
|
|
337
|
+
tar czf %x.tar.gz %~f → tar czf 'mydir.tar.gz' mydir
|
|
338
|
+
echo %m → echo 'file1.txt' 'file2.txt'
|
|
339
|
+
cp %M /tmp %& → cp '/home/user/a.txt' '/home/user/b.txt' /tmp (background)
|
|
340
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
remotes
|