@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
@@ -0,0 +1,69 @@
1
+ // Project: TinyCommand
2
+ // Author: Sebastien Pierre
3
+ // License: BSD
4
+ // Created: 2026-10-17
5
+
6
+ // Module: tcmd/components/OverlayList
7
+ // The shared modal list overlay: the undo history (`u`) and the trash browser
8
+ // (`⇧T`). One component, two modes — `history` lists the journal (a row jumps
9
+ // the file state to that point), `trash` lists trashed entries (a row restores
10
+ // one). The row logic lives in `OverlayRow.js`; this parent maps its `items` to
11
+ // rows and switches title/footer/empty text on the mode.
12
+ //
13
+ // Environment: `overlayBg`, `borderPrimaryActiveFg`, `textHeadingFg` (title),
14
+ // `textSecondaryFg` (footer/empty), `rows(data)` returning the keyed rows and
15
+ // `scrollTop(self, data)` (TUI viewport tracking).
16
+
17
+ import { unwrap } from "select/state/cells"
18
+
19
+ // Function: overlayMode
20
+ // "trash" or "history" for the data's mode (an absent mode reads as history).
21
+ function overlayMode(mode) {
22
+ return unwrap(mode) === "trash" ? "trash" : "history"
23
+ }
24
+
25
+ // Function: overlayTitle
26
+ function overlayTitle(mode) {
27
+ return overlayMode(mode)
28
+ }
29
+
30
+ // Function: overlayFooter
31
+ function overlayFooter(mode) {
32
+ return overlayMode(mode) === "trash"
33
+ ? "↑↓ move ↵ restore Esc close"
34
+ : "↑↓ move ↵ jump ^Z undo ^⇧Z redo Esc close"
35
+ }
36
+
37
+ // Function: overlayEmpty
38
+ // The empty-state line: no history yet, or an empty trash.
39
+ function overlayEmpty(mode) {
40
+ return overlayMode(mode) === "trash" ? " trash is empty" : " no history yet"
41
+ }
42
+
43
+ // Function: createBehaviors
44
+ // Builds the OverlayList behavior map for the given renderer environment.
45
+ function createBehaviors(env) {
46
+ const behaviors = {
47
+ bg: () => env.overlayBg,
48
+ borderFg: () => env.borderPrimaryActiveFg,
49
+ titleColor: () => env.textHeadingFg,
50
+ title: (_self, data) => overlayTitle(data.mode),
51
+ footer: (_self, data) => overlayFooter(data.mode),
52
+ footerFg: () => env.textSecondaryFg,
53
+ empty: (_self, data) => overlayEmpty(data.mode),
54
+ emptyFg: () => env.textSecondaryFg,
55
+ emptyVisible: (_self, data) => (unwrap(data.items)?.length ?? 0) === 0,
56
+ open: (_self, data) => Boolean(unwrap(data.open)),
57
+ }
58
+ if (env?.rows) {
59
+ behaviors.rows = (self, data) => env.rows(self, data)
60
+ }
61
+ if (env?.scrollTop) {
62
+ behaviors.scrollTop = (self, data) => env.scrollTop(self, data)
63
+ }
64
+ return behaviors
65
+ }
66
+
67
+ export { createBehaviors, overlayEmpty, overlayFooter, overlayMode, overlayTitle }
68
+
69
+ // EOF
@@ -2,11 +2,8 @@
2
2
  <box width="72%" height="60%" border borderStyle="rounded"
3
3
  out:title="title" out:titleColor="titleColor" out:borderColor="borderFg"
4
4
  out:backgroundColor="bg" flexDirection="column">
5
- <box flexDirection="row" height="1" paddingLeft="1">
6
- <text out="sign" out:fg="titleColor"></text>
7
- <input ref="query" inout="query" flexGrow="1" height="1" out:focused="focused" on:enter="confirm"></input>
8
- </box>
9
- <scrollbox ref="list" flexGrow="1">
5
+ <text when="emptyVisible" out="empty" out:fg="emptyFg" paddingLeft="1" wrapMode="none"></text>
6
+ <scrollbox when="!emptyVisible" ref="list" flexGrow="1">
10
7
  <box out="rows" flexDirection="column"></box>
11
8
  </scrollbox>
12
9
  <box flexDirection="row" height="1" paddingLeft="1">
@@ -0,0 +1,5 @@
1
+ <li class="overlay-row" out:class="rowClass" out:data-current="rowCurrent" out:data-dim="rowDim" on:click="item.key!overlay:select">
2
+ <span class="overlay-marker" out="rowMarker"></span>
3
+ <span class="overlay-text" out="rowText"></span>
4
+ <span class="overlay-meta" out="rowMeta"></span>
5
+ </li>
@@ -0,0 +1,47 @@
1
+ // Project: TinyCommand
2
+ // Author: Sebastien Pierre
3
+ // License: BSD
4
+ // Created: 2026-10-17
5
+
6
+ // Module: tcmd/components/OverlayRow
7
+ // One row of the shared list overlay (`u` history / `⇧T` trash). Reads
8
+ // `{ item: { text, meta, dim }, index, currentRow }`; the builder snapshots the
9
+ // overlay cursor into a plain `currentRow` boolean, with the shared `cursor`
10
+ // cell as fallback for hand-built bags.
11
+ //
12
+ // Environment: `accentSecondaryFg`, `textFg`, `textSecondaryFg`,
13
+ // `textHeadingFg`, `cursorBg` (current row background), `currentClass` (web).
14
+
15
+ import { unwrap } from "select/state/cells"
16
+
17
+ // Function: rowCurrent
18
+ // Whether the row is the overlay cursor: the plain `currentRow` flag wins over
19
+ // the shared `cursor` cell.
20
+ function rowCurrent(data) {
21
+ if (data.currentRow !== undefined) return data.currentRow === true
22
+ return data.index === unwrap(data.cursor)
23
+ }
24
+
25
+ // Function: createBehaviors
26
+ // Builds the OverlayRow behavior map for the given renderer environment.
27
+ function createBehaviors(env) {
28
+ return {
29
+ rowBg: (_self, data) => (rowCurrent(data) ? env.cursorBg : undefined),
30
+ rowClass: (_self, data) => (rowCurrent(data) ? env.currentClass : null),
31
+ rowCurrent: (_self, data) => `${rowCurrent(data)}`,
32
+ rowDim: (_self, data) => `${Boolean(data.item?.dim)}`,
33
+ rowMarker: (_self, data) => (rowCurrent(data) ? "> " : " "),
34
+ rowMarkerFg: () => env.accentSecondaryFg,
35
+ rowText: (_self, data) => data.item?.text ?? "",
36
+ // The cursor row reads bright; an undone/dim row (a superseded history
37
+ // entry) takes the secondary colour, like a hint.
38
+ rowTextFg: (_self, data) =>
39
+ rowCurrent(data) ? env.textHeadingFg : data.item?.dim ? env.textSecondaryFg : env.textFg,
40
+ rowMeta: (_self, data) => data.item?.meta ?? "",
41
+ rowMetaFg: () => env.textSecondaryFg,
42
+ }
43
+ }
44
+
45
+ export { createBehaviors, rowCurrent }
46
+
47
+ // EOF
@@ -0,0 +1,9 @@
1
+ <box flexDirection="row" height="1" columnGap="1" out:backgroundColor="rowBg" on:click="item.key!overlay:select">
2
+ <text out="rowMarker" width="2" out:fg="rowMarkerFg"></text>
3
+ <box flexGrow="1" minWidth="0" overflow="hidden">
4
+ <text out="rowText" out:fg="rowTextFg" wrapMode="none" truncate="true"></text>
5
+ </box>
6
+ <box flexShrink="0" overflow="hidden">
7
+ <text out="rowMeta" out:fg="rowMetaFg" wrapMode="none" truncate="true"></text>
8
+ </box>
9
+ </box>
@@ -1,3 +1,3 @@
1
1
  <div class="summary" ref="box">
2
- <span out="line" class="summary-line"></span>
2
+ <span out="priorityLine" class="summary-line"></span>
3
3
  </div>
@@ -49,18 +49,13 @@ function summaryShortDisk(disk) {
49
49
  function createBehaviors(env) {
50
50
  const measure = env.measure ?? ((text) => `${text}`.length)
51
51
  return {
52
- line: (self, data) => {
53
- const text = summaryText(unwrap(data.entries), unwrap(data.disk))
54
- const width = env.width ? env.width(self) : 0
55
- return env.elide ? env.elide(text, width) : text
56
- },
57
52
  lineFg: () => env.lineFg,
58
53
  // TUI: degrade by information value instead of chopping mid-token:
59
54
  // full line -> short disk -> counts only -> elided counts.
60
55
  priorityLine: (self, data) => {
61
56
  const entries = unwrap(data.entries)
62
57
  const disk = unwrap(data.disk)
63
- const width = env.width ? env.width(self) : 0
58
+ const width = env.width ? env.width(self, data) : 0
64
59
  const full = summaryText(entries, disk)
65
60
  if (!width || measure(full) <= width) return full
66
61
  const counts = `${INDENT}${summaryCounts(entries)}`
@@ -5,7 +5,10 @@
5
5
 
6
6
  // Module: tcmd/components/format
7
7
  // Presentation-only formatting helpers (sizes, counts, header title) shared by
8
- // every component for every renderer.
8
+ // every component for every renderer. Size and disk formatting come from
9
+ // `core/format` because operations report the same values.
10
+
11
+ import { formatDisk, formatSize } from "../core/format.js"
9
12
 
10
13
  // Function: formatTitle
11
14
  // Header line `TinyCmd - HOST DIR/BASE` for the given directory. When it
@@ -78,28 +81,6 @@ function elideCrumbs(segments, width) {
78
81
  return [marker, ...rest]
79
82
  }
80
83
 
81
- // Function: formatSize
82
- // Human-readable byte size; directories pass through their own label.
83
- function formatSize(bytes) {
84
- const value = Number(bytes ?? 0)
85
- if (value < 1024) return `${value}B`
86
- const units = ["K", "M", "G", "T"]
87
- let size = value / 1024
88
- let unit = 0
89
- while (size >= 1024 && unit < units.length - 1) {
90
- size /= 1024
91
- unit++
92
- }
93
- return `${size >= 100 ? Math.round(size) : size.toFixed(1)}${units[unit]}`
94
- }
95
-
96
- // Function: formatDisk
97
- // Compact free/total disk line, e.g. "12G free of 256G".
98
- function formatDisk(disk) {
99
- if (!disk) return ""
100
- return `${formatSize(disk.free)} free of ${formatSize(disk.total)}`
101
- }
102
-
103
84
  // Function: formatTime
104
85
  // Local `YYYY-MM-DD HH:MM` stamp for a millisecond epoch; empty when absent.
105
86
  function formatTime(ms) {
@@ -4,11 +4,12 @@
4
4
  // Created: 2026-10-09
5
5
 
6
6
  // Module: tcmd/components/icons
7
- // Nerd Font icon for a file entry, in the spirit of yazi: a kind icon
8
- // (directory, symlink, device node) wins, then a per-extension icon, then the
9
- // generic file icon. A few basenames map directly too, so `Makefile`,
7
+ // Nerd Font icons, in the spirit of yazi. `iconFor` maps a file entry to its
8
+ // kind icon (directory, symlink, device node), then a per-extension icon, then
9
+ // the generic file icon; a few basenames map directly too, so `Makefile`,
10
10
  // `Dockerfile` and dotfiles like `.gitignore` get their icon even without a
11
- // distinguishing extension.
11
+ // distinguishing extension. `sourceMarker` maps a command-panel entry's
12
+ // namespace (a CLI operation or a TinyCmd action) to its marker.
12
13
  //
13
14
  // The glyphs are Nerd Font codepoints (from `glyphnames.json`), so they only
14
15
  // render on a terminal patched with a Nerd Font: rows show them when the
@@ -27,6 +28,20 @@ const SYMLINK_ICON = "\u{f0c1}" // nf-fa-link
27
28
  const DEVICE_ICON = "\u{e795}" // nf-dev-terminal
28
29
  const FILE_ICON = "\u{f016}" // nf-fa-file_o
29
30
 
31
+ // Constant: SOURCE_ICON / SOURCE_TAG
32
+ // The namespace marker the command panel puts in front of each entry name:
33
+ // a terminal prompt for a CLI operation, a bolt for a TinyCmd action. Browsers
34
+ // lack the Nerd Font (and `TCMD_ICONS=0` opts out), so the same marker exists
35
+ // as a three-character tag that keeps the name column aligned either way.
36
+ const SOURCE_ICON = {
37
+ cli: "\u{ebca}", // nf-cod-terminal_bash
38
+ app: "\u{f0e7}", // nf-fa-bolt
39
+ }
40
+ const SOURCE_TAG = {
41
+ cli: "cli",
42
+ app: "app",
43
+ }
44
+
30
45
  // Constant: NAME_ICONS
31
46
  // Basename (lowercase, leading dot stripped) → glyph, checked before the
32
47
  // extension table.
@@ -164,6 +179,7 @@ function extensionOf(name) {
164
179
  // file icon.
165
180
  function iconFor(entry) {
166
181
  if (!entry) return ""
182
+ if (entry.add) return entry.isDir ? DIR_ICON : FILE_ICON
167
183
  if (entry.isSymlink) return SYMLINK_ICON
168
184
  if (entry.isDevice) return DEVICE_ICON
169
185
  if (entry.isDir) return DIR_ICON
@@ -172,6 +188,141 @@ function iconFor(entry) {
172
188
  return NAME_ICONS[plain] ?? EXTENSION_ICONS[extensionOf(name)] ?? FILE_ICON
173
189
  }
174
190
 
191
+ // Function: lucideIconFor
192
+ // Lucide's outline icons are used by the web renderers, where SVG works without
193
+ // a patched terminal font. The TUI keeps the detailed Nerd Font mapping above;
194
+ // the web uses a smaller set of semantic file-kind icons.
195
+ const LUCIDE_NAME_ICONS = {
196
+ cmakelists: "file-code",
197
+ dockerfile: "file-code",
198
+ env: "settings",
199
+ gitattributes: "settings",
200
+ gitconfig: "settings",
201
+ gitignore: "settings",
202
+ gitmodules: "settings",
203
+ gnumakefile: "file-code",
204
+ license: "file-text",
205
+ licence: "file-text",
206
+ makefile: "file-code",
207
+ npmrc: "settings",
208
+ readme: "file-text",
209
+ }
210
+ const LUCIDE_EXTENSION_ICONS = {
211
+ "7z": "file-archive",
212
+ aac: "audio-lines",
213
+ avi: "video",
214
+ bmp: "file-image",
215
+ bz2: "file-archive",
216
+ c: "file-code",
217
+ cc: "file-code",
218
+ cjs: "file-code",
219
+ clj: "file-code",
220
+ cmake: "file-code",
221
+ csv: "file-text",
222
+ cfg: "settings",
223
+ conf: "settings",
224
+ cpp: "file-code",
225
+ cs: "file-code",
226
+ css: "file-code",
227
+ d: "file-code",
228
+ db: "database",
229
+ diff: "file-code",
230
+ ex: "file-code",
231
+ exs: "file-code",
232
+ env: "settings",
233
+ flac: "audio-lines",
234
+ gif: "file-image",
235
+ go: "file-code",
236
+ gql: "file-code",
237
+ graphql: "file-code",
238
+ gz: "file-archive",
239
+ h: "file-code",
240
+ hbs: "file-code",
241
+ hs: "file-code",
242
+ hpp: "file-code",
243
+ htm: "file-code",
244
+ html: "file-code",
245
+ ini: "settings",
246
+ java: "file-code",
247
+ jar: "file-archive",
248
+ jpeg: "file-image",
249
+ jpg: "file-image",
250
+ js: "file-code",
251
+ jsx: "file-code",
252
+ json: "file-code",
253
+ kt: "file-code",
254
+ kts: "file-code",
255
+ less: "file-code",
256
+ lock: "settings",
257
+ log: "file-text",
258
+ lua: "file-code",
259
+ m4a: "audio-lines",
260
+ md: "file-text",
261
+ markdown: "file-text",
262
+ mk: "file-code",
263
+ mkv: "video",
264
+ mov: "video",
265
+ mp3: "audio-lines",
266
+ mp4: "video",
267
+ mjs: "file-code",
268
+ nim: "file-code",
269
+ ogg: "audio-lines",
270
+ pdf: "file-text",
271
+ perl: "file-code",
272
+ patch: "file-code",
273
+ php: "file-code",
274
+ pl: "file-code",
275
+ png: "file-image",
276
+ ps1: "file-code",
277
+ py: "file-code",
278
+ r: "file-code",
279
+ rb: "file-code",
280
+ rar: "file-archive",
281
+ rs: "file-code",
282
+ sass: "file-code",
283
+ scala: "file-code",
284
+ scss: "file-code",
285
+ sh: "file-code",
286
+ sql: "file-code",
287
+ sqlite: "database",
288
+ sqlite3: "database",
289
+ svg: "file-image",
290
+ swift: "file-code",
291
+ tar: "file-archive",
292
+ text: "file-text",
293
+ toml: "settings",
294
+ ts: "file-code",
295
+ tsv: "file-text",
296
+ txt: "file-text",
297
+ tsx: "file-code",
298
+ vim: "file-code",
299
+ vue: "file-code",
300
+ wav: "audio-lines",
301
+ webm: "video",
302
+ webp: "file-image",
303
+ xml: "file-code",
304
+ xz: "file-archive",
305
+ yaml: "file-code",
306
+ yml: "file-code",
307
+ zip: "file-archive",
308
+ zsh: "file-code",
309
+ }
310
+
311
+ function lucideIconFor(entry) {
312
+ if (!entry) return ""
313
+ if (entry.add) return "folder-plus"
314
+ if (entry.isSymlink) return "link-2"
315
+ if (entry.isDevice) return "cpu"
316
+ if (entry.isDir) return "folder"
317
+
318
+ const name = `${entry.name ?? ""}`
319
+ const basename = name.slice(name.lastIndexOf("/") + 1)
320
+ const extension = extensionOf(basename)
321
+ const stem = extension ? basename.slice(0, -(extension.length + 1)) : basename
322
+ const plain = (basename.startsWith(".") ? basename.slice(1) : basename).toLowerCase()
323
+ return LUCIDE_NAME_ICONS[plain] ?? LUCIDE_NAME_ICONS[stem.toLowerCase()] ?? LUCIDE_EXTENSION_ICONS[extension] ?? "file"
324
+ }
325
+
175
326
  // Function: iconsEnabled
176
327
  // Whether rows show icons: on by default, `TCMD_ICONS=0|false|off` opts out
177
328
  // for terminals without a Nerd Font.
@@ -180,6 +331,26 @@ function iconsEnabled(environment = globalThis.process?.env ?? {}) {
180
331
  return value !== "0" && value !== "false" && value !== "off"
181
332
  }
182
333
 
183
- export { DEVICE_ICON, DIR_ICON, FILE_ICON, SYMLINK_ICON, extensionOf, iconFor, iconsEnabled }
334
+ // Function: sourceMarker
335
+ // The marker a command-panel entry carries: the Nerd Font glyph when icons are
336
+ // on, the three-character tag otherwise (browsers lack the font).
337
+ function sourceMarker(source, icons) {
338
+ if (icons) return SOURCE_ICON[source] ?? ""
339
+ return SOURCE_TAG[source] ?? ""
340
+ }
341
+
342
+ export {
343
+ DEVICE_ICON,
344
+ DIR_ICON,
345
+ FILE_ICON,
346
+ SOURCE_ICON,
347
+ SOURCE_TAG,
348
+ SYMLINK_ICON,
349
+ extensionOf,
350
+ iconFor,
351
+ iconsEnabled,
352
+ lucideIconFor,
353
+ sourceMarker,
354
+ }
184
355
 
185
356
  // EOF
@@ -11,7 +11,7 @@
11
11
 
12
12
  // Binding
13
13
  export { bind } from "./bind.js"
14
- export { commandBarMarks, commandBarMarksItem, commandBarRowCount, commandBarStatus } from "./CommandBar.js"
14
+ export { commandBarFit, commandBarMarks, commandBarMarksItem, commandBarStatus } from "./CommandBar.js"
15
15
 
16
16
  // Behaviors
17
17
  export { createBehaviors as appBehaviors, createEvents as appEvents, createInit as appInit } from "./App.js"
@@ -22,20 +22,24 @@ export { createBehaviors as commandBarBehaviors } from "./CommandBar.js"
22
22
  export { createBehaviors as commandItemBehaviors } from "./CommandItem.js"
23
23
  export { createBehaviors as commandArgRowBehaviors } from "./CommandArgRow.js"
24
24
  export { createBehaviors as commandPanelBehaviors } from "./CommandPanel.js"
25
- export { createBehaviors as commandPaletteBehaviors } from "./CommandPalette.js"
26
25
  export { createBehaviors as confirmBehaviors } from "./Confirm.js"
27
26
  export { createBehaviors as crumbBehaviors } from "./Crumb.js"
28
27
  export { createBehaviors as entryDetailBehaviors } from "./EntryDetail.js"
29
28
  export { createBehaviors as fileListBehaviors } from "./FileList.js"
29
+ export { createBehaviors as filePickerBehaviors } from "./FilePicker.js"
30
+ export { createBehaviors as filePickerRowBehaviors } from "./FilePickerRow.js"
30
31
  export { createBehaviors as filePreviewBehaviors } from "./FilePreview.js"
31
32
  export { createBehaviors as fileRowBehaviors } from "./FileRow.js"
32
33
  export { createBehaviors as filterBarBehaviors } from "./FilterBar.js"
33
- export { createBehaviors as paletteRowBehaviors } from "./PaletteRow.js"
34
+ export { createBehaviors as helpBehaviors } from "./Help.js"
35
+ export { createRowBehaviors as helpRowBehaviors } from "./Help.js"
36
+ export { createBehaviors as overlayListBehaviors } from "./OverlayList.js"
37
+ export { createBehaviors as overlayRowBehaviors } from "./OverlayRow.js"
34
38
  export { createBehaviors as summaryBehaviors } from "./Summary.js"
35
39
 
36
40
  // Helpers
37
41
  export { formatCounts, formatDisk, formatMode, formatSize, formatTime } from "./format.js"
38
- export { iconFor, iconsEnabled } from "./icons.js"
39
- export { bookmarkRows, commandItemRows, crumbRows, fileRows, fileWindow, paletteRows, panelArgRows, panelOpRows } from "./rows.js"
42
+ export { iconFor, iconsEnabled, lucideIconFor, sourceMarker } from "./icons.js"
43
+ export { bookmarkRows, commandItemRows, crumbRows, filePickerRows, fileRows, fileWindow, helpRows, listingWindow, overlayRows, panelArgRows, panelOpRows, panelRunRows } from "./rows.js"
40
44
 
41
45
  // EOF