@ssebastien/tcmd 0.1.3 → 0.1.7

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.
Files changed (134) hide show
  1. package/README.md +256 -62
  2. package/bin/tcmd +10 -2
  3. package/bin/tcmd.js +13 -2
  4. package/desktop/dist/index.html +13 -0
  5. package/desktop/dist/main.js +20298 -0
  6. package/desktop/dist/style.css +1515 -0
  7. package/desktop/frontend/index.html +13 -0
  8. package/package.json +19 -6
  9. package/public/fonts/OFL.txt +93 -0
  10. package/public/index.html +14 -0
  11. package/public/licenses/LUCIDE.txt +43 -0
  12. package/public/main.js +20679 -0
  13. package/public/style.css +1515 -0
  14. package/scripts/build-desktop.js +31 -0
  15. package/scripts/serve-web.js +61 -0
  16. package/src/js/tcmd/components/App.html +80 -14
  17. package/src/js/tcmd/components/App.js +339 -114
  18. package/src/js/tcmd/components/App.xml +76 -22
  19. package/src/js/tcmd/components/BookmarkList.html +5 -5
  20. package/src/js/tcmd/components/BookmarkList.js +15 -41
  21. package/src/js/tcmd/components/BookmarkList.xml +1 -2
  22. package/src/js/tcmd/components/BookmarkRow.html +2 -1
  23. package/src/js/tcmd/components/BookmarkRow.js +14 -9
  24. package/src/js/tcmd/components/BookmarkRow.xml +1 -1
  25. package/src/js/tcmd/components/Breadcrumbs.js +1 -1
  26. package/src/js/tcmd/components/CommandArgRow.html +28 -0
  27. package/src/js/tcmd/components/CommandArgRow.js +146 -30
  28. package/src/js/tcmd/components/CommandArgRow.xml +55 -4
  29. package/src/js/tcmd/components/CommandBar.html +2 -2
  30. package/src/js/tcmd/components/CommandBar.js +33 -27
  31. package/src/js/tcmd/components/CommandBar.xml +2 -2
  32. package/src/js/tcmd/components/CommandItem.html +1 -1
  33. package/src/js/tcmd/components/CommandItem.js +25 -25
  34. package/src/js/tcmd/components/CommandItem.xml +2 -2
  35. package/src/js/tcmd/components/CommandPanel.html +29 -0
  36. package/src/js/tcmd/components/CommandPanel.js +78 -52
  37. package/src/js/tcmd/components/CommandPanel.xml +49 -25
  38. package/src/js/tcmd/components/Confirm.html +2 -2
  39. package/src/js/tcmd/components/EntryDetail.html +7 -7
  40. package/src/js/tcmd/components/EntryDetail.js +22 -8
  41. package/src/js/tcmd/components/FileList.html +5 -1
  42. package/src/js/tcmd/components/FileList.js +2 -1
  43. package/src/js/tcmd/components/FilePicker.html +10 -0
  44. package/src/js/tcmd/components/FilePicker.js +43 -0
  45. package/src/js/tcmd/components/FilePicker.xml +13 -0
  46. package/src/js/tcmd/components/FilePickerRow.html +6 -0
  47. package/src/js/tcmd/components/FilePickerRow.js +27 -0
  48. package/src/js/tcmd/components/FilePickerRow.xml +10 -0
  49. package/src/js/tcmd/components/FilePreview.html +14 -4
  50. package/src/js/tcmd/components/FilePreview.js +111 -12
  51. package/src/js/tcmd/components/FilePreview.xml +28 -8
  52. package/src/js/tcmd/components/FileRow.html +2 -1
  53. package/src/js/tcmd/components/FileRow.js +70 -12
  54. package/src/js/tcmd/components/FilterBar.html +1 -1
  55. package/src/js/tcmd/components/FilterBar.js +14 -4
  56. package/src/js/tcmd/components/FilterBar.xml +1 -1
  57. package/src/js/tcmd/components/Frame.js +324 -0
  58. package/src/js/tcmd/components/Help.html +15 -0
  59. package/src/js/tcmd/components/Help.js +260 -0
  60. package/src/js/tcmd/components/Help.xml +13 -4
  61. package/src/js/tcmd/components/HelpRow.html +8 -0
  62. package/src/js/tcmd/components/HelpRow.xml +7 -0
  63. package/src/js/tcmd/components/OverlayList.html +8 -0
  64. package/src/js/tcmd/components/OverlayList.js +69 -0
  65. package/src/js/tcmd/components/{CommandPalette.xml → OverlayList.xml} +2 -5
  66. package/src/js/tcmd/components/OverlayRow.html +5 -0
  67. package/src/js/tcmd/components/OverlayRow.js +47 -0
  68. package/src/js/tcmd/components/OverlayRow.xml +9 -0
  69. package/src/js/tcmd/components/Summary.html +1 -1
  70. package/src/js/tcmd/components/Summary.js +1 -6
  71. package/src/js/tcmd/components/format.js +4 -23
  72. package/src/js/tcmd/components/icons.js +176 -5
  73. package/src/js/tcmd/components/index.js +9 -5
  74. package/src/js/tcmd/components/rows.js +176 -48
  75. package/src/js/tcmd/core/base64.js +64 -0
  76. package/src/js/tcmd/core/bookmarks.js +45 -8
  77. package/src/js/tcmd/core/commands.js +978 -260
  78. package/src/js/tcmd/core/concurrency.js +59 -0
  79. package/src/js/tcmd/core/decompress.js +117 -0
  80. package/src/js/tcmd/core/directory.js +219 -93
  81. package/src/js/tcmd/core/format.js +36 -0
  82. package/src/js/tcmd/core/fs.js +438 -54
  83. package/src/js/tcmd/core/history.js +58 -0
  84. package/src/js/tcmd/core/index.js +3 -9
  85. package/src/js/tcmd/core/journal.js +93 -0
  86. package/src/js/tcmd/core/keymap.js +42 -6
  87. package/src/js/tcmd/core/operations.js +813 -48
  88. package/src/js/tcmd/core/os.js +29 -3
  89. package/src/js/tcmd/core/pane.js +33 -8
  90. package/src/js/tcmd/core/panel.js +280 -104
  91. package/src/js/tcmd/core/path.js +30 -3
  92. package/src/js/tcmd/core/permissions.js +107 -0
  93. package/src/js/tcmd/core/picker.js +156 -0
  94. package/src/js/tcmd/core/preview.js +102 -0
  95. package/src/js/tcmd/core/script.js +7 -95
  96. package/src/js/tcmd/core/selection.js +9 -1
  97. package/src/js/tcmd/core/session.js +498 -200
  98. package/src/js/tcmd/core/watch.js +153 -0
  99. package/src/js/tcmd/desktop/backend/api.js +165 -0
  100. package/src/js/tcmd/desktop/backend/fs.js +626 -0
  101. package/src/js/tcmd/desktop/backend/main.ts +9 -0
  102. package/src/js/tcmd/desktop/bookmarks.js +23 -0
  103. package/src/js/tcmd/desktop/fs.js +91 -0
  104. package/src/js/tcmd/desktop/main.js +194 -0
  105. package/src/js/tcmd/desktop/os.js +33 -0
  106. package/src/js/tcmd/index.js +0 -1
  107. package/src/js/tcmd/tui/app.js +118 -60
  108. package/src/js/tcmd/tui/catalogue.js +238 -42
  109. package/src/js/tcmd/tui/index.js +18 -19
  110. package/src/js/tcmd/tui/opentui.js +33 -0
  111. package/src/js/tcmd/tui/syntax.js +91 -0
  112. package/src/js/tcmd/tui/theme.js +32 -2
  113. package/src/js/tcmd/vendor/bzip2.js +276 -0
  114. package/src/js/tcmd/web/app.js +225 -41
  115. package/src/js/tcmd/web/bookmarks.js +36 -0
  116. package/src/js/tcmd/web/catalogue.js +81 -38
  117. package/src/js/tcmd/web/effects.js +223 -0
  118. package/src/js/tcmd/web/header.html +1 -1
  119. package/src/js/tcmd/web/header.js +15 -11
  120. package/src/js/tcmd/web/http.js +13 -0
  121. package/src/js/tcmd/web/index.js +2 -2
  122. package/src/js/tcmd/web/main.js +6 -66
  123. package/src/js/tcmd/web/opfs.js +297 -30
  124. package/src/js/tcmd/web/seed.js +4 -4
  125. package/src/js/tcmd/web/server.js +124 -21
  126. package/src/js/tcmd/web/theme.js +4 -0
  127. package/tinyjs.json +16 -0
  128. package/src/js/tcmd/components/CommandPalette.html +0 -7
  129. package/src/js/tcmd/components/CommandPalette.js +0 -58
  130. package/src/js/tcmd/components/PaletteRow.html +0 -3
  131. package/src/js/tcmd/components/PaletteRow.js +0 -43
  132. package/src/js/tcmd/components/PaletteRow.xml +0 -4
  133. package/src/js/tcmd/tui/frame.js +0 -269
  134. package/src/js/tcmd/tui/help.js +0 -32
package/README.md CHANGED
@@ -10,18 +10,78 @@
10
10
 
11
11
  TinyCommand `tcmd` is a midnight-commander-like CLI and desktop tool:
12
12
 
13
- - Browse files in two panes (the left is the source, the right the
14
- destination)
15
- - Named bookmarks: `b` records the cursor entry (or the pane directory) under
16
- a name defaulting to its basename, remembering whether it is a file or a
17
- directory; `g` opens a fzf-style picker over bookmark names and paths. A
18
- directory bookmark opens to show its contents, a file bookmark is selected
19
- and previewed in its directory.
20
- - Operations over the current panes: copy, move, delete/erase, stat, touch,
21
- selection buffers, edit/view/open and shell commands
22
- - Command palette with UI actions, plus a scripting mode (`:`) that completes
23
- operations and the available filesets ($A, $B, …, $FILE)
24
- - Keyboard and mouse navigation, inline file previews
13
+ - Browse files in the three-column explorer (the opening view) — the parent
14
+ directory, the current directory (slightly wider) and the preview — or in
15
+ the two-pane commander (the left is the source, the right the destination),
16
+ with `t` toggling between the two views. Clicking any column, preview
17
+ included, gives it the focus.
18
+ - Named bookmarks: `b` opens one bookmarks panel. Type to filter the stored
19
+ bookmarks by name or path; when the filter is empty and the cursor entry (or
20
+ the pane directory) is not bookmarked yet, a first row adds it under a
21
+ basename-derived name. `Enter` opens the selected bookmark, `⌫`/`Delete`
22
+ removes it. A directory bookmark opens to show its contents, a file bookmark
23
+ is selected and previewed in its directory.
24
+ - Undo/redo (`⌃Z` / `⌃⇧Z` or `⌃Y`) over recorded filesystem changes (mkdir,
25
+ copies to new destinations, move, rename, trashed delete), with a history list
26
+ (`u`); a delete reports the trash directory it used and can be undone or
27
+ restored from the trash browser
28
+ (`⇧T`), which lists trashed entries by their original path. A double click on
29
+ a row opens it like `Enter`.
30
+ - Operations over the current panes: copy, move, mkdir/new, hardlink/symlink,
31
+ chmod/chown, delete/erase/unlink, stat, du/df, touch, selection buffers,
32
+ edit/view/open and shell commands; `abspath` and `realpath` show
33
+ copy-pasteable absolute paths and copy them to the clipboard, with the latter
34
+ following symlinks. `unlink` removes links only, never their targets.
35
+ - Command panel (`:`), the one entry point, a two-pane palette: the left pane
36
+ filters (`:xxx`) over CLI operations grouped by purpose: **Command**,
37
+ **File**, **Info**, **Create**, **Edit** (amber), and **Delete** (red). The
38
+ Command group also has the workspace-view toggle and Quit; File's `view`
39
+ previews the selected path; Info holds `stat`, `du` and `df`; amber Edit also
40
+ holds `copy` and the metadata changes. Bold section
41
+ titles head indented command names, alphabetized within each category. The right
42
+ pane lists the selected command's arguments under its name — editable values
43
+ use filled fields, while read-only values are plain and muted; `new`/`mkdir`
44
+ show the current `PATH` above a `NAME`, and `chmod`/`chown` list `PATH`
45
+ first. Selections use
46
+ a checkbox (multiple) / radio (single)
47
+ list of the selected paths wrapped in its own border, with a lone path shown
48
+ read-only. `chmod`'s `MODE` is a compact u/g/o grid of clickable R/W/X cells
49
+ (arrow keys move between cells, Space toggles), seeded from the selected
50
+ file's current mode. Labels and values sit one space in from the pane border
51
+ (` PATH`, ` [value]`), and `[Run]` follows the last field after a
52
+ blank line, aligned left in the command category's color. Amber (Edit) and
53
+ red (Delete) actions take two steps: the first Run turns the action into
54
+ `[Confirm]` and prints the command below it in the risk colour, the second
55
+ executes. A failed run shows `[Retry]` with a framed error block below it,
56
+ while a
57
+ successful result briefly replaces the command bar at the bottom. A selection
58
+ argument draws from the *focused* column, so in the explorer focusing the parent mirror
59
+ selects the directory under its cursor. `Tab` cycles the filter, arguments and
60
+ Run button; `Enter` runs a complete command or jumps to the next missing
61
+ argument, and a `[Close Esc]` close button sits in the dialog's top border,
62
+ upper right. `!` lands straight on the shell
63
+ command. A destination/path argument browses the filesystem in a file picker
64
+ (`⌃O`): its path is editable as a string, the matching entries sit below and
65
+ render like a file panel (icons, aligned sizes, scrollbar), with `..` pinned
66
+ at the top. Explorer mode has no destination pane, so a move or copy there
67
+ asks where to. A `replace` operation replaces a literal `FIND` with `REPLACE`
68
+ across the selection — one file replaces its whole name — previewing each
69
+ `old → new` mapping before it runs. Exact `rename FROM TO PATH` renames one named
70
+ child inside `PATH`.
71
+ - Keyboard and mouse navigation, inline file previews — a previewed directory
72
+ renders as a pane (its breadcrumbs over its own listing), in both views, a
73
+ source file highlights with tree-sitter (JavaScript, TypeScript, Markdown and
74
+ Zig today) through the same `code` renderable, and an image draws in place
75
+ through the terminal's graphics protocol (Kitty/sixel, half-block fallback),
76
+ the metadata card staying for formats with no decoder. `.gz` and `.bz2`
77
+ entries preview their decompressed contents: the inner name drives the kind
78
+ and highlighter (`notes.txt.gz` heads as `<GZ · TXT>`), and a body that
79
+ decompresses to binary stays a card
80
+ - Symlinks are shown *and* followed: a link row prints `name -> target`
81
+ (`notes.md -> notes.txt`, `docs/ -> ../shared`), a link to a directory is a
82
+ directory (it sorts, opens and previews as one), and the preview reads
83
+ through it — the header reads `notes.md -> notes.txt <TXT>` and the kind
84
+ comes from the target's extension, not the link's name
25
85
  - Bookmarks and quick find (later milestones)
26
86
 
27
87
  References: [Marta.sh](https://marta.sh) for the UI, [fman.io](https://fman.io)
@@ -46,39 +106,88 @@ bun run tcmd # open cwd (left) and ~ (right)
46
106
  bun run tcmd /tmp ~ # open specific directories
47
107
  ```
48
108
 
49
- Keys: `↑/↓` move (`^↑/^↓` jump a few lines), `Enter/→` open, `←/Bksp` parent,
50
- `Esc`/`⇧Esc` history back/forward, `Tab`/`⇧Tab` switch panes
51
- (`⇧Tab` goes back), `Space` mark, `b` bookmark the cursor entry (or the pane
52
- directory) under a basename-defaulted name, `g` jump to a bookmark, `:` or `m`
53
- open the command panel (Commando: operations, their arguments, and the live
54
- resolved command), `v` preview (fullscreen viewer when the terminal is narrow),
55
- `O`/`E` open/edit the previewed file, `/` fuzzy-filter the active pane (`Enter`
56
- keeps it, `Esc` clears), `^P` command palette, `!` run a shell command, `^R`
57
- reload, `?` key reference, `q` quit.
58
-
59
- The workspace opens with a slim identity bar (`TinyCmd - HOST`) over an
60
- unframed tiling of pane boxes (the two middle edges sit side by
61
- side) labelled **SRC** (left, source) / **DST** (right, destination), with a
109
+ Keys: `↑/↓` move (`^↑/^↓` jump a few lines), `Enter/→` open (a directory
110
+ re-opens with the cursor it was left with, a text file opens in `$EDITOR`, and
111
+ anything that cannot be edited — an image, media, a binary, or any file on a
112
+ host with no editor — opens the fullscreen viewer), `←/Bksp` parent (the cursor
113
+ lands on the directory you left), `Esc`/`⇧Esc` history back/forward, `Tab`/`⇧Tab` switch panes
114
+ (`⇧Tab` goes back), `t` toggle the commander/explorer view, `Space` mark, `b`
115
+ open the bookmarks panel (filter, add the current path, `Enter` open,
116
+ `⌫`/`Del` remove), `:` open the
117
+ command panel (the one entry point for CLI operations, grouped by risk with
118
+ editable arguments, an explicit Run button and the inline file picker on `⌃O`),
119
+ `v` preview (fullscreen viewer when
120
+ the terminal is narrow), `⇧V` maximize the preview over the columns and back
121
+ again (in either full view, `↑`/`↓` scroll a file preview and `←`/`→` step to
122
+ the previous/next entry), `O`/`E` open/edit the previewed file, `/` fuzzy-filter
123
+ the active pane (`Enter` keeps it, `Esc` clears), `!` open the panel on the
124
+ shell command, `F5`/`r`/`^R` reload, `?` key reference, `q` quit.
125
+
126
+ Listings refresh themselves when the filesystem changes: the TUI and desktop
127
+ views watch the displayed directories natively, while the web view polls them
128
+ every few seconds. Hidden columns pause their watcher and re-read when shown.
129
+ A refresh keeps the selected entry selected, so files written by other
130
+ programs appear without moving the cursor. The keys above (and the web toolbar
131
+ button) remain for a forced re-read.
132
+
133
+ The workspace opens in explorer mode with a slim identity bar (`TinyCmd -
134
+ HOST`) — the view mode and version trailing at its right, the mode word drawn
135
+ letter by letter along the logo's own gradients (`explorer` in the Tiny blues,
136
+ `commander` in the Cmd oranges, clicking it toggles the view) and the version
137
+ dim after it — over an
138
+ unframed tiling of pane boxes (the two middle edges sit side by side) labelled
139
+ **SRC** (left, source) / **DST** (right, destination) in commander, with a
62
140
  header row (label + clickable breadcrumbs — collapsed to first letters like
63
141
  `/h/s/W/Perso/tcmd` when the width demands — + the inline filter) separated by a
64
- rule from the listing, an entry-detail line for the selected entry, and one
65
- summary row, so the two panes stay aligned. The detail line shows
142
+ rule from the listing, and an entry-detail line for the selected entry. The
143
+ detail line shows
66
144
  `date · type · size · rights` with the rights right-aligned, the date and size
67
- bright, and the size/rights dropping out when the pane is too narrow; the
68
- summary row is indented to match. The active pane's border and header
69
- rule are highlighted and its cursor row uses the strong cursor colour, while
70
- the inactive pane stays dim with a muted cursor. Rows lead with a yazi-style
145
+ bright, and the size/rights dropping out when the pane is too narrow. The active
146
+ pane's border and header rule take the view mode's colour — the Tiny blue in
147
+ explorer, the gold accent in commander — and its cursor row uses the strong
148
+ cursor colour, while
149
+ the inactive pane stays dim with a muted cursor. TUI rows lead with a yazi-style
71
150
  Nerd Font icon (folder/link kind icons plus a per-extension icon, `Makefile`,
72
- `Dockerfile` and dotfiles included), on unless `TCMD_ICONS=0` — it needs a
73
- Nerd-Font-patched terminal font, and the web view never shows icons. A
151
+ `Dockerfile` and dotfiles included), on unless `TCMD_ICONS=0`; it needs a
152
+ Nerd-Font-patched terminal font. The web and desktop views use local Lucide SVG
153
+ icons for toolbar actions, file kinds, bookmarks, and Help key badges; command
154
+ panel entries show a chevron to indicate their argument pane. A
74
155
  context-aware inspector sits
75
156
  on the right when the terminal is at least 100 columns wide, and becomes a
76
- fullscreen viewer below that. The status bar lists every binding as a bracket
77
- shorthand (`[Open ⏎]`) wrapping over as many rows as the width demands — the
78
- marked count trails the list, and a notice or error swaps the keys for a
79
- one-line status. Below 64×16
157
+ fullscreen viewer below that; `⇧V` expands it over the whole pane row —
158
+ parent and current included — and brings the previous layout back on the next
159
+ press. The status bar keeps its fixed bracket shorthands on one line
160
+ (`[Commands :]`, `[Mark Spc]`, `[Filter /]`, `[Switch ⇥]`, `[Bookmarks b]`,
161
+ `[Toggle Mode t]`, `[Help ?]`) and elides entries behind `…` when the width
162
+ runs out — the marked count trails the list, and a notice or error swaps the
163
+ keys for a one-line status. Below 64×16
80
164
  the app shows a resize hint instead of the workspace.
81
165
 
166
+ ### View modes
167
+
168
+ `t` flips the workspace between two modes:
169
+
170
+ - **explorer** (the default) leads with the parent of the current directory,
171
+ so the three columns read parent · current · preview — a Miller chain with
172
+ the current column slightly wider than its neighbours;
173
+ - **commander** tiles source and destination.
174
+
175
+ The parent and preview columns are mirrors: they own their cursor, filter and
176
+ marks, but opening an entry, stepping up, following one of their breadcrumbs or
177
+ clicking one of their rows moves the pane they follow, which is what keeps the
178
+ mirror in step (in the explorer the previewed directory *is* the current
179
+ column's own child, so the chain never drifts). A click on the parent column
180
+ makes the clicked directory the current one — or, for a file, opens its
181
+ directory with the file selected and previewed — while a click inside the
182
+ previewed directory promotes that directory to current with the clicked entry
183
+ selected (focus stays on the column that was clicked). The parent mirror's
184
+ cursor also follows the current
185
+ directory: it lands on the entry the current pane shows, so the chain always
186
+ highlights where it is. `Tab` cycles through the columns the current mode shows,
187
+ and clicking any column — the preview included — gives it the focus. A
188
+ previewed directory always renders as a pane, in both modes: its breadcrumbs
189
+ over its own listing, instead of a directory card.
190
+
82
191
  ### Web version
83
192
 
84
193
  The web view runs the same core on select.js and offers two filesystem
@@ -89,28 +198,104 @@ backends:
89
198
  - **HTTP API** (automatic fallback, or `?fs=http`): the server exposes the
90
199
  real filesystem over `/api/*`, using the same Node adapter as the TUI.
91
200
 
201
+ It uses the same command panel (`:`), selection/filter and history keys as the
202
+ TUI, plus `?` for the key reference. Pane focus, marks,
203
+ entry details and command risk colours follow the terminal presentation. The
204
+ inspector sits beside the panes when wide and opens with `v` as a fullscreen
205
+ viewer when narrow (`Esc` closes it); `⇧V` maximizes it over the pane row and
206
+ restores the layout. Bookmarks persist in browser storage,
207
+ separately for each filesystem backend and HTTP root; they are not shared with
208
+ the TUI's bookmark file. Storage denial leaves bookmarks usable for the session.
209
+
92
210
  ```sh
211
+ tcmd --server # installed command: serve the web UI without the TUI
93
212
  make run-local # build and serve both backends (alias to `bun run web`)
94
213
  bun run web # build and serve both backends on http://localhost:8787
95
214
  bun run web:build # bundle only, into public/main.js
96
215
  bun run web:serve # serve the existing bundle plus /api/*
97
216
  ```
98
217
 
218
+ From a checkout, `./bin/tcmd --server` (or `bun run tcmd --server`) does the
219
+ same thing, building the browser bundle if it is missing. Installed packages
220
+ include the bundle. Open http://localhost:8787/?fs=http for your actual files,
221
+ or http://localhost:8787/?fs=opfs for the browser-storage demo. Stop with Ctrl-C.
222
+ Use `HOST=0.0.0.0 PORT=9000 tcmd --server` to change the address and port
223
+ (default `127.0.0.1:8787`), or
224
+ `TCMD_WEB_ROOT=/some/dir tcmd --server` to confine the filesystem API. `HOST`
225
+ takes an IP address (`0.0.0.0` or `::` for every interface); a machine name
226
+ resolves to several addresses and the server binds only one of them.
227
+
99
228
  `make run-local` is the local dev entry point (it wraps `bun run web` and
100
229
  honours `TCMD_WEB_PORT`, default 8787). The Makefile bootstraps the LittleSDK
101
230
  into `deps/sdk` for the standard `make` targets.
102
231
 
103
232
  Appending `?fs=opfs` or `?fs=http` to the URL forces one backend. The HTTP
104
233
  backend needs the page served by `bun run web:serve` (a plain static server
105
- only supports OPFS); the server binds to loopback and requires a per-process
106
- token injected into `index.html`. `TCMD_WEB_ROOT=/some/dir` confines the API
234
+ only supports OPFS); the server binds to loopback by default (`HOST`
235
+ overrides it) and requires a per-process token injected into `index.html`.
236
+ `TCMD_WEB_ROOT=/some/dir` confines the API
107
237
  (default `/`, the same access as `bun run tcmd`), but it is not a sandbox:
108
- don't expose the port. With OPFS, deleting moves entries to `/.trash`; the API
109
- uses the desktop trash. Open/edit/run are unavailable in the browser either
110
- way (the null host adapter reports it in the command bar); everything else —
111
- navigation, marks, scripting, copy/move/erase — works on either
238
+ don't expose the port. Symlinks that resolve outside the root are rejected;
239
+ root and destination checks are not race-proof against another local process
240
+ changing the filesystem during a request. With OPFS, deleting moves entries to `/.trash`;
241
+ the API uses the desktop trash. OPFS coordination across tabs uses Web Locks
242
+ when available; without them, writes are coordinated per adapter only.
243
+ Open/edit/run are unavailable in the browser
244
+ either way (the null host adapter reports it in the command bar); everything
245
+ else — navigation, marks, scripting, copy/move/erase — works on either
112
246
  backend. `public/index.html` is the host page; `public/main.js` is generated.
113
247
 
248
+ ### Desktop app (tinyjs)
249
+
250
+ The native desktop target reuses the web presentation and shared session, but
251
+ uses a tinyjs backend for real filesystem access. It runs without Bun/Node in
252
+ the shipped app and does not start an HTTP server or open a port.
253
+
254
+ Install [tinyjs](https://tinyjs.app/) and the project dependencies, then run:
255
+
256
+ ```sh
257
+ bun install
258
+ bun run desktop:dev # bundle the webview UI, then open the native window
259
+ bun run desktop:build # tinyjs build; emits a platform-native app/package
260
+ ```
261
+
262
+ `tinyjs.json` configures the app. `tinyjs build` bundles the desktop frontend
263
+ through `desktop:bundle`; the source page is `desktop/frontend/index.html`.
264
+ For frontend changes during development, rerun `bun run desktop:bundle` before
265
+ reloading the window. The desktop uses the system webview and requires tinyjs
266
+ 0.57.0 or newer.
267
+
268
+ The desktop window keeps the native title bar, hides the native menu, and uses
269
+ an in-app toolbar for folder navigation, refresh, view/preview toggles, commands
270
+ and help. Its toolbar replaces the TinyCmd identity header; the browser UI keeps
271
+ that header. Press F6 to move focus between the workspace and toolbar; toolbar
272
+ buttons use native Tab/Enter/Space keys, and Escape returns to the workspace.
273
+
274
+ The web UI uses the system sans-serif font for controls and chrome, with the
275
+ M+1Code Nerd Font Mono bundled for filenames, paths, and file content. The
276
+ font is from Nerd Fonts v3.5.1 and is distributed under SIL OFL 1.1; its license
277
+ is included in `public/fonts/OFL.txt`.
278
+ The UI icons are from Lucide and distributed under ISC (with the listed Feather
279
+ icons under MIT); the license is included in `public/licenses/LUCIDE.txt`.
280
+
281
+ On Linux, the tinyjs launcher also needs the Ayatana AppIndicator runtime. On
282
+ Fedora install it with `sudo dnf install libayatana-appindicator-gtk3`; on
283
+ Debian/Ubuntu use `sudo apt install libayatana-appindicator3-1`. Install the
284
+ WebKitGTK runtime required by tinyjs as well.
285
+
286
+ Filesystem operations are performed by the backend using txiki.js APIs. Text
287
+ files open in the operating system's default editor; `!` / shell commands run
288
+ in a non-interactive shell and display captured output in a native dialog.
289
+ Trash uses the Freedesktop trash under `$XDG_DATA_HOME` (or
290
+ `~/.local/share/Trash`), including undo/restore. The current desktop adapter
291
+ supports POSIX paths on Linux and macOS. Windows drive/UNC paths are not yet
292
+ supported; the app reports this rather than attempting file operations.
293
+
294
+ The desktop backend has full access to the current user's files, and the
295
+ command panel intentionally exposes shell execution. Treat commands and
296
+ filesystem prompts accordingly. Browser and TUI builds remain separate and
297
+ continue to use their existing backends.
298
+
114
299
  ## Architecture
115
300
 
116
301
  MVC with a presentation-agnostic core, so a web UI can share the same model
@@ -125,26 +310,26 @@ src/js/tcmd/
125
310
  entries.js filter/sort over file entries (dirs first)
126
311
  selection.js cursor/mark arithmetic
127
312
  directory.js one pane's directory: path, entries, view, cursor, marks
128
- pane.js one directory, proxying its cells
313
+ pane.js one directory, proxying its cells + currentPane resolution
129
314
  bookmarks.js named bookmarks (path + file/dir kind) + the memory/file stores
130
- panel.js command panel: operation args bound from the live context
131
- session.js two panes + active pane, preview, palette, panel, dispatch
315
+ panel.js grouped CLI command panel, with arguments bound from context
316
+ session.js two panes + parent/preview mirrors, view mode, active pane, preview, panel, dispatch
132
317
  keymap.js key chords -> command ids
133
318
  commands.js command registry; commands are `run(session, payload)`
134
- operations.js scripting operations (copy/move/delete/erase/stat/sel/…)
135
- script.js scripting line parser, runner and palette completions
319
+ operations.js CLI operations (copy/move/delete/erase/stat/sel/…)
320
+ script.js scripting line parser and runner
136
321
  components/ Shared, renderer-agnostic component logic
137
322
  App.js root controller: key + event -> dispatch
138
323
  bind.js `ui.component(name).does(behaviors).using(source)` binding
139
324
  rows.js keyed row-bag builders shared by both catalogues
140
325
  FileList.js … one `.js` per component (behaviors + pure helpers)
141
326
  FileRow.js … row behaviors shared by both engines
327
+ Frame.js workspace chrome, breakpoints and command-bar geometry
328
+ Help.js key-reference overlay behaviors + the two-column key text
142
329
  format.js presentation helpers (sizes/counts)
143
330
  tui/ View (select.tui templates on OpenTUI)
144
331
  app.js binds App.js + App.xml + TUI environment; size/help cells
145
332
  catalogue.js binds each `components/*.js` to its `.xml` and registers tags
146
- frame.js TUI-only chrome: frame title, breakpoints
147
- help.js TUI-only `?` key-reference overlay behaviors
148
333
  index.js TUI entry point; feeds `size` from renderer resize events
149
334
  theme.js TUI palette resolver (dark/light/mono, NO_COLOR)
150
335
  web/ Browser view (select.js templates on the DOM, same core)
@@ -152,6 +337,8 @@ src/js/tcmd/
152
337
  catalogue.js binds each `components/*.js` to its `.html`
153
338
  index.js web entry point: `main(session, selector)`
154
339
  main.js browser bootstrap: backend choice + session (bundled)
340
+ effects.js instance-owned focus, key trapping, resize and scroll effects
341
+ bookmarks.js localStorage bookmark store, scoped by backend and API root
155
342
  opfs.js OPFS filesystem adapter (browser storage)
156
343
  http.js filesystem adapter over the `/api` JSON endpoints
157
344
  server.js dev server: `public/` + `/api/*` (used by `bun run web:serve`)
@@ -177,9 +364,13 @@ reactive collections, sharing row behaviors over a `{ item, index, … }` bag
177
364
  built by `components/rows.js`; cells stay in the bag and are read at the point
178
365
  of use.
179
366
 
180
- TUI-only components live under `tui/` (`frame.js`, `help.js`) and their `.xml`
181
- templates under `components/`; they are registered only by `tui/catalogue.js`.
182
- Web-only components live under `web/` (`header.js` + `header.html`). Shared
367
+ `components/Frame.js` holds the renderer-free workspace chrome (brand bar,
368
+ responsive breakpoints, pane borders, command-bar geometry) and
369
+ `components/Help.js` holds the key reference — behaviours and the two-column
370
+ key text, with every distinct keymap chord shown in the command bar's bracket
371
+ style — used by both views. Web-only components live under `web/` (`header.js` +
372
+ `header.html`, plus the DOM effects in `effects.js` and the bookmark store in
373
+ `bookmarks.js`). Shared
183
374
  behaviors only ever grow additively (a new key for a new presentation),
184
375
  so the other engine's presentation and the parity tests keep working unchanged.
185
376
 
@@ -206,8 +397,8 @@ Decoupling rules:
206
397
  see `src/js/tcmd/vendor/`)
207
398
  - Bun as the runtime
208
399
 
209
- `deps/` symlinks to sibling checkouts; `scripts/link-deps.sh` (run as
210
- postinstall) re-links `node_modules` so upstream edits propagate.
400
+ `deps/` symlinks to sibling checkouts; run `scripts/link-deps.sh` after
401
+ `bun install` to re-link `node_modules` so upstream edits propagate.
211
402
 
212
403
  ## Tests
213
404
 
@@ -222,14 +413,17 @@ tests run the shared logic through select.js on a happy-dom document.
222
413
  ## Roadmap
223
414
 
224
415
  - [x] Walking skeleton: two panes, navigation, breadcrumbs, summary, command
225
- bar, command palette, text preview
226
- - [x] File operations (copy/move/delete/erase/stat/touch/sel), scripting mode
227
- with the `$A`/`$B` pane filesets
228
- - [x] Scripting completion (operations, filesets, active-directory and nested
229
- paths)
416
+ bar, command panel, text preview
417
+ - [x] File operations (copy/move/delete/erase/stat/touch/sel), with the
418
+ `$A`/`$B` pane filesets and `$FILE` still resolving in any argument
419
+ - [x] Grouped command panel (`:`): white / amber / red risk sections, editable
420
+ arguments, an explicit Run action and an inline picker; `!` lands on shell
230
421
  - [x] Pane fuzzy filtering (`/` to filter, `Enter` to keep, `Esc` to clear)
231
- - [x] Named bookmarks (`b` records, `g` jumps, persisted to a JSON file)
422
+ - [x] Named bookmarks (`b` opens the panel to add, filter, open or remove them,
423
+ persisted to a JSON file)
232
424
  - [ ] Quick find, grep
233
- - [ ] Image/PDF/audio/video previews
425
+ - [x] Image previews (terminal graphics protocol) and tree-sitter source
426
+ highlighting in the inspector
427
+ - [ ] PDF/audio/video previews
234
428
  - [x] Web view on the same core (shared components rendered by select.js)
235
429
  - [x] Pane navigation history (`Esc`/`⇧Esc` back/forward)
package/bin/tcmd CHANGED
@@ -5,13 +5,21 @@ if ! command -v bun >/dev/null 2>&1; then
5
5
  exit 1
6
6
  fi
7
7
  script=$0
8
+ case "$script" in
9
+ */*) ;;
10
+ *) script=$(command -v "$script") ;;
11
+ esac
12
+ case "$script" in
13
+ /*) ;;
14
+ *) script=$PWD/$script ;;
15
+ esac
8
16
  while [ -L "$script" ]; do
9
- dir=$(CDPATH= cd -- "$(dirname -- "$script")" && pwd)
17
+ dir=$(CDPATH= cd -P -- "$(dirname -- "$script")" && pwd)
10
18
  link=$(readlink "$script")
11
19
  case "$link" in
12
20
  /*) script=$link ;;
13
21
  *) script=$dir/$link ;;
14
22
  esac
15
23
  done
16
- dir=$(CDPATH= cd -- "$(dirname -- "$script")" && pwd)
24
+ dir=$(CDPATH= cd -P -- "$(dirname -- "$script")" && pwd)
17
25
  exec bun "$dir/tcmd.js" "$@"
package/bin/tcmd.js CHANGED
@@ -1,4 +1,15 @@
1
1
  #!/usr/bin/env bun
2
2
  // TinyCommand `tcmd` entry point.
3
- const paths = process.argv.slice(2).filter((arg) => arg !== "-")
4
- await import("../src/js/tcmd/tui/index.js").then((tui) => tui.main(paths))
3
+ const raw = process.argv.slice(2).filter((arg) => arg !== "-")
4
+ // `--server` is a mode flag, not a path: accept it in any position.
5
+ const server = raw.includes("--server")
6
+ const paths = raw.filter((arg) => arg !== "--server")
7
+ if (server) {
8
+ if (paths.length > 0) {
9
+ console.error("Usage: tcmd --server (configure with HOST, PORT and TCMD_WEB_ROOT)")
10
+ process.exit(1)
11
+ }
12
+ await import("../scripts/serve-web.js").then((web) => web.main())
13
+ } else {
14
+ await import("../src/js/tcmd/tui/index.js").then((tui) => tui.main(paths))
15
+ }
@@ -0,0 +1,13 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>TinyCommand</title>
7
+ <link rel="stylesheet" href="./style.css" />
8
+ </head>
9
+ <body>
10
+ <div id="app"></div>
11
+ <script type="module" src="./main.js"></script>
12
+ </body>
13
+ </html>