commandertui 1.0.0__py3-none-any.whl
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.
- commandertui/__init__.py +1 -0
- commandertui/app.py +40 -0
- commandertui/ascii_font.py +27 -0
- commandertui/bookmarks.py +133 -0
- commandertui/boot.py +114 -0
- commandertui/cli.py +22 -0
- commandertui/compare_screen.py +146 -0
- commandertui/diff.py +61 -0
- commandertui/executor.py +134 -0
- commandertui/main_screen.py +211 -0
- commandertui/modals.py +250 -0
- commandertui/models.py +79 -0
- commandertui/plan.py +50 -0
- commandertui/progress_screen.py +137 -0
- commandertui/run_queue.py +83 -0
- commandertui/scanner.py +115 -0
- commandertui/sync.py +149 -0
- commandertui/theme.py +94 -0
- commandertui/trash.py +49 -0
- commandertui/widgets.py +134 -0
- commandertui-1.0.0.dist-info/METADATA +191 -0
- commandertui-1.0.0.dist-info/RECORD +26 -0
- commandertui-1.0.0.dist-info/WHEEL +5 -0
- commandertui-1.0.0.dist-info/entry_points.txt +2 -0
- commandertui-1.0.0.dist-info/licenses/LICENSE +21 -0
- commandertui-1.0.0.dist-info/top_level.txt +1 -0
commandertui/widgets.py
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
|
|
6
|
+
from textual.app import ComposeResult
|
|
7
|
+
from textual.containers import Vertical
|
|
8
|
+
from textual.reactive import reactive
|
|
9
|
+
from textual.widgets import DataTable, Static
|
|
10
|
+
|
|
11
|
+
PARENT_MARK = ".."
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _human_size(n: int) -> str:
|
|
15
|
+
size = float(n)
|
|
16
|
+
for unit in ("B", "K", "M", "G", "T"):
|
|
17
|
+
if size < 1024 or unit == "T":
|
|
18
|
+
return f"{size:.0f}{unit}" if unit == "B" else f"{size:.1f}{unit}"
|
|
19
|
+
size /= 1024
|
|
20
|
+
return f"{size:.1f}T"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class FilePanel(Vertical):
|
|
24
|
+
"""One side of the dual-pane view: a single-directory listing you can
|
|
25
|
+
navigate, mark entries in, and read the current path from.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
path: reactive[str] = reactive(os.path.expanduser("~"))
|
|
29
|
+
active: reactive[bool] = reactive(False)
|
|
30
|
+
show_hidden: reactive[bool] = reactive(False)
|
|
31
|
+
|
|
32
|
+
def __init__(self, start_path: str, side: str, **kwargs) -> None:
|
|
33
|
+
super().__init__(**kwargs)
|
|
34
|
+
self.side = side # "left" | "right"
|
|
35
|
+
self.marked: set[str] = set()
|
|
36
|
+
self.path = os.path.abspath(start_path)
|
|
37
|
+
self._rows: list[tuple[str, bool]] = [] # (name, is_dir), in display order
|
|
38
|
+
|
|
39
|
+
def compose(self) -> ComposeResult:
|
|
40
|
+
yield Static(self.path, id=f"{self.side}-pathbar", classes="pathbar")
|
|
41
|
+
table = DataTable(id=f"{self.side}-table", cursor_type="row")
|
|
42
|
+
table.add_columns(" ", "Name", "Size", "Modified")
|
|
43
|
+
yield table
|
|
44
|
+
|
|
45
|
+
def on_mount(self) -> None:
|
|
46
|
+
self.refresh_listing()
|
|
47
|
+
|
|
48
|
+
def watch_active(self, active: bool) -> None:
|
|
49
|
+
bar = self.query_one(f"#{self.side}-pathbar", Static)
|
|
50
|
+
bar.set_class(active, "active")
|
|
51
|
+
|
|
52
|
+
def refresh_listing(self) -> None:
|
|
53
|
+
table = self.query_one(f"#{self.side}-table", DataTable)
|
|
54
|
+
table.clear()
|
|
55
|
+
self._rows = []
|
|
56
|
+
|
|
57
|
+
entries: list[os.DirEntry] = []
|
|
58
|
+
try:
|
|
59
|
+
with os.scandir(self.path) as it:
|
|
60
|
+
entries = sorted(
|
|
61
|
+
(e for e in it if self.show_hidden or not e.name.startswith(".")),
|
|
62
|
+
key=lambda e: (not e.is_dir(), e.name.lower()),
|
|
63
|
+
)
|
|
64
|
+
except (PermissionError, FileNotFoundError):
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
if os.path.dirname(self.path) != self.path:
|
|
68
|
+
table.add_row("", PARENT_MARK, "", "", key=PARENT_MARK)
|
|
69
|
+
self._rows.append((PARENT_MARK, True))
|
|
70
|
+
|
|
71
|
+
for entry in entries:
|
|
72
|
+
try:
|
|
73
|
+
stat = entry.stat(follow_symlinks=False)
|
|
74
|
+
except (PermissionError, FileNotFoundError):
|
|
75
|
+
continue
|
|
76
|
+
is_dir = entry.is_dir(follow_symlinks=False)
|
|
77
|
+
mark = "*" if entry.path in self.marked else ""
|
|
78
|
+
size = "<DIR>" if is_dir else _human_size(stat.st_size)
|
|
79
|
+
mtime = datetime.fromtimestamp(stat.st_mtime).strftime("%Y-%m-%d %H:%M")
|
|
80
|
+
display_name = f"{entry.name}/" if is_dir else entry.name
|
|
81
|
+
table.add_row(mark, display_name, size, mtime, key=entry.name)
|
|
82
|
+
self._rows.append((entry.name, is_dir))
|
|
83
|
+
|
|
84
|
+
bar = self.query_one(f"#{self.side}-pathbar", Static)
|
|
85
|
+
bar.update(self.path)
|
|
86
|
+
|
|
87
|
+
def selected_name(self) -> str | None:
|
|
88
|
+
table = self.query_one(f"#{self.side}-table", DataTable)
|
|
89
|
+
if table.row_count == 0:
|
|
90
|
+
return None
|
|
91
|
+
row_key = table.coordinate_to_cell_key(table.cursor_coordinate).row_key
|
|
92
|
+
return row_key.value if row_key is not None else None
|
|
93
|
+
|
|
94
|
+
def enter_selected(self) -> None:
|
|
95
|
+
name = self.selected_name()
|
|
96
|
+
if name is None:
|
|
97
|
+
return
|
|
98
|
+
if name == PARENT_MARK:
|
|
99
|
+
self.path = os.path.dirname(self.path)
|
|
100
|
+
else:
|
|
101
|
+
candidate = os.path.join(self.path, name)
|
|
102
|
+
if os.path.isdir(candidate):
|
|
103
|
+
self.path = candidate
|
|
104
|
+
self.marked.clear()
|
|
105
|
+
self.refresh_listing()
|
|
106
|
+
|
|
107
|
+
def toggle_mark_selected(self) -> None:
|
|
108
|
+
name = self.selected_name()
|
|
109
|
+
if name is None or name == PARENT_MARK:
|
|
110
|
+
return
|
|
111
|
+
full = os.path.join(self.path, name)
|
|
112
|
+
if full in self.marked:
|
|
113
|
+
self.marked.discard(full)
|
|
114
|
+
mark = ""
|
|
115
|
+
else:
|
|
116
|
+
self.marked.add(full)
|
|
117
|
+
mark = "*"
|
|
118
|
+
|
|
119
|
+
# Update just the mark cell in place rather than calling
|
|
120
|
+
# refresh_listing(): rebuilding the whole table resets the cursor to
|
|
121
|
+
# row 0, so marking an item used to bounce you straight back to the
|
|
122
|
+
# top -- annoying when marking several items in a row.
|
|
123
|
+
table = self.query_one(f"#{self.side}-table", DataTable)
|
|
124
|
+
table.update_cell_at(table.cursor_coordinate._replace(column=0), mark)
|
|
125
|
+
|
|
126
|
+
def marked_or_selected(self) -> list[str]:
|
|
127
|
+
"""What an action should operate on: the marked set if non-empty,
|
|
128
|
+
otherwise just whatever is under the cursor."""
|
|
129
|
+
if self.marked:
|
|
130
|
+
return sorted(self.marked)
|
|
131
|
+
name = self.selected_name()
|
|
132
|
+
if name and name != PARENT_MARK:
|
|
133
|
+
return [os.path.join(self.path, name)]
|
|
134
|
+
return []
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: commandertui
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Dual-pane retro terminal file manager: compare, sync, copy and move folders
|
|
5
|
+
Author: Humberto Barchini
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/juniorbarchini-oss/commandertui
|
|
8
|
+
Classifier: Environment :: Console
|
|
9
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Topic :: System :: Filesystems
|
|
12
|
+
Requires-Python: >=3.11
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Requires-Dist: textual>=8.0
|
|
16
|
+
Requires-Dist: send2trash>=1.8
|
|
17
|
+
Dynamic: license-file
|
|
18
|
+
|
|
19
|
+
# CommanderTUI
|
|
20
|
+
|
|
21
|
+
Dual-pane terminal file manager: compare two folders, sync them, or move
|
|
22
|
+
files/folders between them (and to USB/SMB/any mounted path) — with a
|
|
23
|
+
phosphor-green 80s-terminal look and a short modem-style boot animation.
|
|
24
|
+
|
|
25
|
+
Status: **v1.0.0** — 40/40 tests passing. MIT licensed.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Why this exists
|
|
30
|
+
|
|
31
|
+
A safe, simple alternative to heavier dual-pane file managers. It was
|
|
32
|
+
written after an earlier attempt of the same idea had to be scrapped for
|
|
33
|
+
running an unauthenticated local HTTP server. This project deliberately:
|
|
34
|
+
|
|
35
|
+
- **Never opens a network port.** Pure terminal UI (Textual), no HTTP server,
|
|
36
|
+
no webview.
|
|
37
|
+
- **Never does a raw `rm -rf`.** Deletes always go through the desktop trash
|
|
38
|
+
(`send2trash` → `gio trash` → manual move into `~/.local/share/Trash`, in
|
|
39
|
+
that order).
|
|
40
|
+
- **Never overwrites in place.** Copies stream into a hidden `.tmp` file next
|
|
41
|
+
to the destination and are committed with `os.replace` — a crash or
|
|
42
|
+
Ctrl+C never leaves a half-written file.
|
|
43
|
+
- **Never guesses on a conflict.** Two-way sync copies each side's exclusive
|
|
44
|
+
files automatically, but a file changed on *both* sides is always shown to
|
|
45
|
+
you, never auto-resolved.
|
|
46
|
+
|
|
47
|
+
## Install
|
|
48
|
+
|
|
49
|
+
**Arch Linux / Omarchy.** An AUR package is on the way (`yay -S commandertui`).
|
|
50
|
+
Until then, build it from the `PKGBUILD` in this repo. `makepkg` pulls in
|
|
51
|
+
everything it needs:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
git clone https://github.com/juniorbarchini-oss/commandertui.git
|
|
55
|
+
cd commandertui/packaging/arch
|
|
56
|
+
makepkg -si
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Remove it with `sudo pacman -R commandertui`.
|
|
60
|
+
|
|
61
|
+
**Other distros.** It is a regular Python package (Python 3.11+):
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
pipx install git+https://github.com/juniorbarchini-oss/commandertui.git
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**From a source checkout** (development):
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
python3 -m venv .venv && .venv/bin/pip install -e . -r requirements-dev.txt
|
|
71
|
+
bin/commandertui [left-dir] [right-dir]
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Run
|
|
75
|
+
|
|
76
|
+
| Command | What it does |
|
|
77
|
+
|---|---|
|
|
78
|
+
| `cmdrtui` | Opens it in its own terminal window. This is also what the **CommanderTUI** app-launcher entry runs |
|
|
79
|
+
| `commandertui` | Runs it inside the terminal you are in |
|
|
80
|
+
| `commandertui DIR1 DIR2` | Opens with those two folders side by side |
|
|
81
|
+
|
|
82
|
+
`cmdrtui` uses your desktop's default terminal (`xdg-terminal-exec`). If that
|
|
83
|
+
isn't available it tries foot, alacritty, kitty and ghostty. The window
|
|
84
|
+
always gets the app-id/class `commandertui`, so a window-manager rule can
|
|
85
|
+
float it. It needs about 1174x637 px to fit every key hint. On Hyprland:
|
|
86
|
+
|
|
87
|
+
```ini
|
|
88
|
+
# hyprland.conf
|
|
89
|
+
windowrulev2 = float, class:^(commandertui)$
|
|
90
|
+
windowrulev2 = size 1174 637, class:^(commandertui)$
|
|
91
|
+
windowrulev2 = center, class:^(commandertui)$
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
```lua
|
|
95
|
+
-- Omarchy Lua config (~/.config/hypr/hyprland.lua)
|
|
96
|
+
o.window("^commandertui$", { float = true, center = true, size = { 1174, 637 } })
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Keys
|
|
100
|
+
|
|
101
|
+
| Key | Action |
|
|
102
|
+
|---|---|
|
|
103
|
+
| `Tab` | Switch active pane |
|
|
104
|
+
| `↑`/`↓`, `Enter` | Navigate, open directory |
|
|
105
|
+
| `Backspace` | Go to parent directory |
|
|
106
|
+
| `Space` | Mark/unmark item under cursor |
|
|
107
|
+
| `F5` | Copy marked (or item under cursor) to the other pane |
|
|
108
|
+
| `F6` | Move marked to the other pane |
|
|
109
|
+
| `F7` | Create a folder in the active pane |
|
|
110
|
+
| `F8` / `Delete` | Delete marked (to trash) |
|
|
111
|
+
| `i` | Size: file/folder count and total bytes of marked items (background, `Esc` stops it) |
|
|
112
|
+
| `h` | Show/hide hidden (dot) files in both panes |
|
|
113
|
+
| `p` | Places: jump to a mounted drive (USB/SMB/rclone) or saved bookmark |
|
|
114
|
+
| `c` | Compare: full recursive diff between the two panes' current folders |
|
|
115
|
+
| `r` | Refresh both panes |
|
|
116
|
+
| `q` | Quit |
|
|
117
|
+
|
|
118
|
+
While a copy/move runs, the progress window shows two bars (current file
|
|
119
|
+
and whole job, by bytes) plus speed in Mb/s and time left. `Esc` asks to
|
|
120
|
+
cancel: finished files stay, the file in progress is discarded (never left
|
|
121
|
+
half-written), and a move never removes a source it didn't fully copy.
|
|
122
|
+
|
|
123
|
+
**Places** lists only what is mounted right now: USB drives
|
|
124
|
+
(`/run/media`, `/media`, `/mnt` — checked with `ismount`, so an empty
|
|
125
|
+
on-demand mount point doesn't show), GVFS/Nautilus network shares, FUSE
|
|
126
|
+
mounts inside `$HOME` (e.g. `rclone mount`), and saved bookmarks.
|
|
127
|
+
|
|
128
|
+
Inside **Compare**:
|
|
129
|
+
|
|
130
|
+
| Key | Action |
|
|
131
|
+
|---|---|
|
|
132
|
+
| `m` | Mirror: make right identical to left |
|
|
133
|
+
| `u` | Update: copy new/changed left → right, never deletes |
|
|
134
|
+
| `b` | Two-way: propagate each side's exclusive changes; conflicts (changed on both sides) are listed, never guessed |
|
|
135
|
+
| `Space` | Mark a row for manual handling |
|
|
136
|
+
| `c` | Copy marked rows in whichever direction they diverge |
|
|
137
|
+
| `Esc` | Back to the panes |
|
|
138
|
+
|
|
139
|
+
Every bulk or destructive action shows a plain-language summary (count of
|
|
140
|
+
operations, total bytes) and requires `y` to confirm.
|
|
141
|
+
|
|
142
|
+
## Architecture
|
|
143
|
+
|
|
144
|
+
```
|
|
145
|
+
commandertui/
|
|
146
|
+
├── models.py # FileEntry, DiffEntry, OperationQueue, QueuedOp
|
|
147
|
+
├── scanner.py # os.scandir recursive tree walk
|
|
148
|
+
├── diff.py # pure comparison: left-only / right-only / modified / identical
|
|
149
|
+
├── plan.py # diff -> OperationQueue (mirror / update / two-way)
|
|
150
|
+
├── sync.py # atomic copy/move (streamed, tmp+replace), checksum verify
|
|
151
|
+
├── trash.py # 3-layer safe delete (send2trash / gio trash / manual)
|
|
152
|
+
├── executor.py # runs an OperationQueue against the filesystem
|
|
153
|
+
├── bookmarks.py # detected mounts (USB/SMB/rclone) + saved places
|
|
154
|
+
├── theme.py # green/amber phosphor palettes
|
|
155
|
+
├── boot.py # startup "modem" reveal animation
|
|
156
|
+
├── widgets.py # FilePanel: one pane's directory listing
|
|
157
|
+
├── modals.py # Confirm / Places / Input / Size / Message screens
|
|
158
|
+
├── progress_screen.py # two-bar transfer progress + cancel
|
|
159
|
+
├── run_queue.py # confirm -> run queue in a worker thread
|
|
160
|
+
├── compare_screen.py # full diff view + bulk/manual sync actions
|
|
161
|
+
├── app.py # CommanderApp: wires panes + keybindings
|
|
162
|
+
└── cli.py # argument parsing, entrypoint
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Tests
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
pip install -r requirements-dev.txt
|
|
169
|
+
pytest
|
|
170
|
+
black commandertui tests # formatter, line length set in pyproject.toml
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Engine (scanner/diff/plan/sync/executor) is fully unit tested against a real
|
|
174
|
+
filesystem (`tmp_path`). The UI has smoke tests via Textual's `Pilot`
|
|
175
|
+
(boot sequence dismisses, pane switching, mark→copy→confirm end to end).
|
|
176
|
+
|
|
177
|
+
## Not in scope
|
|
178
|
+
|
|
179
|
+
No file preview, no deduplication engine, no desktop GUI wrapper — those are
|
|
180
|
+
different problems.
|
|
181
|
+
|
|
182
|
+
## Credits
|
|
183
|
+
|
|
184
|
+
Designed and tested by Humberto Barchini. The code was written together
|
|
185
|
+
with [Claude](https://www.anthropic.com/claude) (Anthropic) through
|
|
186
|
+
[Claude Code](https://claude.com/claude-code): **Claude Sonnet 5** built the
|
|
187
|
+
first version, **Claude Opus 5.5** did most of v0.2 and the v1.0 packaging.
|
|
188
|
+
|
|
189
|
+
## License
|
|
190
|
+
|
|
191
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
commandertui/__init__.py,sha256=J-j-u0itpEFT6irdmWmixQqYMadNl1X91TxUmoiLHMI,22
|
|
2
|
+
commandertui/app.py,sha256=Y992S7qSt1q5weamKyAAh1nKeg3RDhIRMfdUt5TsDts,1284
|
|
3
|
+
commandertui/ascii_font.py,sha256=4wRQ0xQ-AXFPvyayciGQvFQot-KX5K0vCuVPmY4Q63w,1098
|
|
4
|
+
commandertui/bookmarks.py,sha256=dQ8xqbam3b27fu-BA0ZNaZWKY0tC5DWcltS5KAZTbWc,4719
|
|
5
|
+
commandertui/boot.py,sha256=8N8GQhVk5ul2luDtAPj-IYDIzA5mk7_unGVurvmA5pY,3567
|
|
6
|
+
commandertui/cli.py,sha256=gtD8Igjyp24UfLG14rjDyVUtTMvbYjjOQDjj-PNkm-E,641
|
|
7
|
+
commandertui/compare_screen.py,sha256=4wyPcrQS2xhLu0miEcr4nlU26T2xWXFBGqnQZGK4ylc,5808
|
|
8
|
+
commandertui/diff.py,sha256=79hnNAs1QDW7qjsWzwBtOm5faRuMbnIa4QHZsJc6FyQ,1952
|
|
9
|
+
commandertui/executor.py,sha256=KF3LVa0duk5cLqG17AMtBv0gS3k3Cb8MuIr35FIqVlY,4494
|
|
10
|
+
commandertui/main_screen.py,sha256=OLVBPSBPIcecOWVKqGJvZ6ov_JNWeNwk8-zboecatBg,8207
|
|
11
|
+
commandertui/modals.py,sha256=Xs-vOh4-CUKa-9ScMrz90wCOqGzrygAJ6zKUG-_-IHY,7266
|
|
12
|
+
commandertui/models.py,sha256=Tu5Xdf9t1NMNgoNxKSsk1onYlty2z96J48BKaXUoNH0,1536
|
|
13
|
+
commandertui/plan.py,sha256=sTXxArQblKQ7paVtqjfD0PDAHha-tMtO53fWKiMnmRs,1970
|
|
14
|
+
commandertui/progress_screen.py,sha256=vzo6b6Y9TFVzsporCZMEIzo-IMkOudylXGtTdHqEshU,5005
|
|
15
|
+
commandertui/run_queue.py,sha256=YybH5it1FwiPPMRxUtdQu00c6vEyg19uDC3SkBY3b20,3052
|
|
16
|
+
commandertui/scanner.py,sha256=ZLhN5KKbm64UNyqGGQ_Ab5W0BucKCFaJonxyz0mXwO4,3405
|
|
17
|
+
commandertui/sync.py,sha256=2BcFc4-Qta1Vr9yrxke3wSwWX8gCZmBoG0hdIq_-3Ys,5216
|
|
18
|
+
commandertui/theme.py,sha256=0UZYrmdhb31dHFw666Drg9dkEY2n4jwzMpLl4N3RifY,1983
|
|
19
|
+
commandertui/trash.py,sha256=-tg01AqS3RBA1xBRkqg8MX6bvJlunB3sHe282ROv3zk,1458
|
|
20
|
+
commandertui/widgets.py,sha256=KJdOSTBN8rdRLJh3mar44FZ4JsGWNFYTS0Uv8DpfXxM,4989
|
|
21
|
+
commandertui-1.0.0.dist-info/licenses/LICENSE,sha256=_j5_t7RHiqrm0E1_eF1bAdwTIX2Q09ojfR-c2_lBfVc,1074
|
|
22
|
+
commandertui-1.0.0.dist-info/METADATA,sha256=ybrb244qCLtTsvdJnKmUk2hUZfkV7gv6Pw5rD5k3eWs,7489
|
|
23
|
+
commandertui-1.0.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
24
|
+
commandertui-1.0.0.dist-info/entry_points.txt,sha256=tILtL7qtJSstkglZFqwO6MKE8oszeuZDXU2RnEp5geU,55
|
|
25
|
+
commandertui-1.0.0.dist-info/top_level.txt,sha256=2Vurq5RqhhH2b8mf9XvAdL1fsoh9ft4l6ZKn6laDB8g,13
|
|
26
|
+
commandertui-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Humberto Barchini
|
|
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.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
commandertui
|