@ssebastien/tcmd 0.0.0-stage → 0.1.1

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 (95) hide show
  1. package/README.md +234 -2
  2. package/bin/tcmd +17 -0
  3. package/package.json +37 -5
  4. package/src/js/tcmd/components/App.html +28 -0
  5. package/src/js/tcmd/components/App.js +355 -0
  6. package/src/js/tcmd/components/App.xml +98 -0
  7. package/src/js/tcmd/components/BookmarkList.html +12 -0
  8. package/src/js/tcmd/components/BookmarkList.js +73 -0
  9. package/src/js/tcmd/components/BookmarkList.xml +18 -0
  10. package/src/js/tcmd/components/BookmarkRow.html +5 -0
  11. package/src/js/tcmd/components/BookmarkRow.js +40 -0
  12. package/src/js/tcmd/components/BookmarkRow.xml +10 -0
  13. package/src/js/tcmd/components/Breadcrumbs.js +27 -0
  14. package/src/js/tcmd/components/CommandArgRow.js +90 -0
  15. package/src/js/tcmd/components/CommandArgRow.xml +5 -0
  16. package/src/js/tcmd/components/CommandBar.html +4 -0
  17. package/src/js/tcmd/components/CommandBar.js +126 -0
  18. package/src/js/tcmd/components/CommandBar.xml +4 -0
  19. package/src/js/tcmd/components/CommandItem.html +3 -0
  20. package/src/js/tcmd/components/CommandItem.js +78 -0
  21. package/src/js/tcmd/components/CommandItem.xml +6 -0
  22. package/src/js/tcmd/components/CommandPalette.html +7 -0
  23. package/src/js/tcmd/components/CommandPalette.js +58 -0
  24. package/src/js/tcmd/components/CommandPalette.xml +16 -0
  25. package/src/js/tcmd/components/CommandPanel.js +98 -0
  26. package/src/js/tcmd/components/CommandPanel.xml +35 -0
  27. package/src/js/tcmd/components/Confirm.html +3 -0
  28. package/src/js/tcmd/components/Confirm.js +36 -0
  29. package/src/js/tcmd/components/Confirm.xml +7 -0
  30. package/src/js/tcmd/components/Crumb.html +4 -0
  31. package/src/js/tcmd/components/Crumb.js +48 -0
  32. package/src/js/tcmd/components/Crumb.xml +4 -0
  33. package/src/js/tcmd/components/EntryDetail.html +15 -0
  34. package/src/js/tcmd/components/EntryDetail.js +194 -0
  35. package/src/js/tcmd/components/EntryDetail.xml +16 -0
  36. package/src/js/tcmd/components/FileList.html +1 -0
  37. package/src/js/tcmd/components/FileList.js +41 -0
  38. package/src/js/tcmd/components/FileList.xml +7 -0
  39. package/src/js/tcmd/components/FilePreview.html +5 -0
  40. package/src/js/tcmd/components/FilePreview.js +110 -0
  41. package/src/js/tcmd/components/FilePreview.xml +10 -0
  42. package/src/js/tcmd/components/FileRow.html +5 -0
  43. package/src/js/tcmd/components/FileRow.js +135 -0
  44. package/src/js/tcmd/components/FileRow.xml +10 -0
  45. package/src/js/tcmd/components/FilterBar.html +6 -0
  46. package/src/js/tcmd/components/FilterBar.js +50 -0
  47. package/src/js/tcmd/components/FilterBar.xml +6 -0
  48. package/src/js/tcmd/components/Help.xml +11 -0
  49. package/src/js/tcmd/components/PaletteRow.html +3 -0
  50. package/src/js/tcmd/components/PaletteRow.js +43 -0
  51. package/src/js/tcmd/components/PaletteRow.xml +4 -0
  52. package/src/js/tcmd/components/RowList.html +1 -0
  53. package/src/js/tcmd/components/RowList.xml +1 -0
  54. package/src/js/tcmd/components/Summary.html +3 -0
  55. package/src/js/tcmd/components/Summary.js +77 -0
  56. package/src/js/tcmd/components/Summary.xml +3 -0
  57. package/src/js/tcmd/components/bind.js +20 -0
  58. package/src/js/tcmd/components/format.js +155 -0
  59. package/src/js/tcmd/components/icons.js +185 -0
  60. package/src/js/tcmd/components/index.js +41 -0
  61. package/src/js/tcmd/components/rows.js +212 -0
  62. package/src/js/tcmd/core/bookmarks.js +175 -0
  63. package/src/js/tcmd/core/commands.js +930 -0
  64. package/src/js/tcmd/core/directory.js +194 -0
  65. package/src/js/tcmd/core/entries.js +88 -0
  66. package/src/js/tcmd/core/fs.js +266 -0
  67. package/src/js/tcmd/core/index.js +39 -0
  68. package/src/js/tcmd/core/keymap.js +60 -0
  69. package/src/js/tcmd/core/operations.js +453 -0
  70. package/src/js/tcmd/core/os.js +128 -0
  71. package/src/js/tcmd/core/pane.js +74 -0
  72. package/src/js/tcmd/core/panel.js +383 -0
  73. package/src/js/tcmd/core/path.js +57 -0
  74. package/src/js/tcmd/core/script.js +186 -0
  75. package/src/js/tcmd/core/selection.js +43 -0
  76. package/src/js/tcmd/core/session.js +405 -0
  77. package/src/js/tcmd/index.js +53 -0
  78. package/src/js/tcmd/tui/app.js +140 -0
  79. package/src/js/tcmd/tui/catalogue.js +300 -0
  80. package/src/js/tcmd/tui/frame.js +269 -0
  81. package/src/js/tcmd/tui/help.js +32 -0
  82. package/src/js/tcmd/tui/index.js +104 -0
  83. package/src/js/tcmd/tui/theme.js +177 -0
  84. package/src/js/tcmd/vendor/fzy.js +243 -0
  85. package/src/js/tcmd/web/app.js +154 -0
  86. package/src/js/tcmd/web/catalogue.js +159 -0
  87. package/src/js/tcmd/web/header.html +1 -0
  88. package/src/js/tcmd/web/header.js +38 -0
  89. package/src/js/tcmd/web/http.js +79 -0
  90. package/src/js/tcmd/web/index.js +22 -0
  91. package/src/js/tcmd/web/main.js +151 -0
  92. package/src/js/tcmd/web/opfs.js +282 -0
  93. package/src/js/tcmd/web/seed.js +90 -0
  94. package/src/js/tcmd/web/server.js +191 -0
  95. package/src/js/tcmd/web/theme.js +23 -0
@@ -0,0 +1,175 @@
1
+ // Project: TinyCommand
2
+ // Author: Sebastien Pierre
3
+ // License: BSD
4
+ // Created: 2026-10-09
5
+
6
+ // Module: tcmd/core/bookmarks
7
+ // Named bookmarks: a `{ name -> { path, isDir } }` map the user records with
8
+ // `b` and jumps to with `g`. A bookmark remembers whether its path is a file
9
+ // or a directory (or `null` when only a legacy path is known), so a jump can
10
+ // open a directory but select-and-preview a file. Bookmarks are pure model
11
+ // state (a reactive cell) plus a persistence layer: a `BookmarkStore`
12
+ // interface with an in-memory default (tests, web) and a Node/JSON-file
13
+ // implementation (the TUI). The session only ever sees the store interface, so
14
+ // the browser bundle stays free of Node builtins.
15
+
16
+ import { cell } from "select/state/cells"
17
+
18
+ // Function: normalizeName
19
+ // Trims a bookmark name; empty names are rejected by the callers.
20
+ function normalizeName(name) {
21
+ return `${name ?? ""}`.trim()
22
+ }
23
+
24
+ // Function: isValidName
25
+ // A bookmark name is a non-empty token without whitespace or separator
26
+ // punctuation that would be ambiguous in the list.
27
+ function isValidName(name) {
28
+ const text = normalizeName(name)
29
+ return text.length > 0 && !/[\s/]/.test(text)
30
+ }
31
+
32
+ // Function: normalizeEntry
33
+ // Coerces a stored value to `{ path, isDir }`: a plain string is a legacy
34
+ // path with an unknown kind (`isDir: null`), resolved when it is jumped to.
35
+ function normalizeEntry(value) {
36
+ if (typeof value === "string") return { path: value, isDir: null }
37
+ if (value && typeof value === "object" && typeof value.path === "string") {
38
+ return { path: value.path, isDir: value.isDir === true ? true : value.isDir === false ? false : null }
39
+ }
40
+ return null
41
+ }
42
+
43
+ // Function: normalizeMap
44
+ // Coerces a stored `{ name -> value }` map, dropping unusable rows.
45
+ function normalizeMap(map) {
46
+ const out = {}
47
+ for (const [name, value] of Object.entries(map ?? {})) {
48
+ const entry = normalizeEntry(value)
49
+ if (entry) out[name] = entry
50
+ }
51
+ return out
52
+ }
53
+
54
+ // Function: createMemoryBookmarkStore
55
+ // In-memory store: bookmarks live for the process only. The default, used by
56
+ // tests and the web view (no filesystem surface for config).
57
+ function createMemoryBookmarkStore() {
58
+ return {
59
+ async load() {
60
+ return {}
61
+ },
62
+ async save() {},
63
+ }
64
+ }
65
+
66
+ // Function: createBookmarks
67
+ // Creates the bookmark model backed by `store` (defaults to in-memory) with
68
+ // initial `entries` (a `{ name -> path|{path,isDir} }` map). `load()` fills
69
+ // the cell from the store; `record(name, path, isDir)` sets a bookmark and
70
+ // persists; `remove(name)` deletes one; `clear()` empties the cell.
71
+ function createBookmarks({ store = createMemoryBookmarkStore(), entries = {} } = {}) {
72
+ const bookmarks = { store }
73
+ bookmarks.entries = cell(normalizeMap(entries))
74
+
75
+ bookmarks.load = async () => {
76
+ const loaded = await store.load?.()
77
+ if (loaded && typeof loaded === "object") bookmarks.entries.set(normalizeMap(loaded))
78
+ return bookmarks.entries.value
79
+ }
80
+
81
+ // Function: record
82
+ // Sets `name -> { path, isDir }` and persists. Returns the stored name.
83
+ bookmarks.record = async (name, path, isDir = null) => {
84
+ const clean = normalizeName(name)
85
+ if (!isValidName(clean)) throw new Error("bookmark needs a name")
86
+ bookmarks.entries.set({
87
+ ...bookmarks.entries.value,
88
+ [clean]: { path, isDir: isDir === true ? true : isDir === false ? false : null },
89
+ })
90
+ await store.save?.(bookmarks.entries.value)
91
+ return clean
92
+ }
93
+
94
+ // Function: remove
95
+ // Deletes a bookmark and persists. Returns true when one was removed.
96
+ bookmarks.remove = async (name) => {
97
+ const clean = normalizeName(name)
98
+ if (clean in bookmarks.entries.value) {
99
+ const next = { ...bookmarks.entries.value }
100
+ delete next[clean]
101
+ bookmarks.entries.set(next)
102
+ await store.save?.(next)
103
+ return true
104
+ }
105
+ return false
106
+ }
107
+
108
+ // Function: set
109
+ // Replaces the whole map (e.g. a `bookmarks rm` script op), persisting.
110
+ bookmarks.set = async (map) => {
111
+ bookmarks.entries.set(normalizeMap(map))
112
+ await store.save?.(bookmarks.entries.value)
113
+ }
114
+
115
+ // Function: find
116
+ // The `{ name, path, isDir }` bookmark whose path matches, or null.
117
+ bookmarks.find = (path) => {
118
+ for (const [name, entry] of Object.entries(bookmarks.entries.value)) {
119
+ if (entry.path === path) return { name, ...entry }
120
+ }
121
+ return null
122
+ }
123
+
124
+ // Function: findByName
125
+ // The `{ name, path, isDir }` bookmark for a name, or null.
126
+ bookmarks.findByName = (name) => {
127
+ const entry = bookmarks.entries.value[normalizeName(name)]
128
+ return entry ? { name: normalizeName(name), ...entry } : null
129
+ }
130
+
131
+ // Function: rows
132
+ // List rows `{ name, path, isDir }`, sorted by name, for the jump list.
133
+ bookmarks.rows = () =>
134
+ Object.entries(bookmarks.entries.value)
135
+ .map(([name, entry]) => ({ name, ...entry }))
136
+ .sort((a, b) => a.name.localeCompare(b.name))
137
+
138
+ return bookmarks
139
+ }
140
+
141
+ // Function: createFileBookmarkStore
142
+ // Node/JSON-file store. `readFile`/`writeFile`/`dir` are injected so the core
143
+ // carries no Node import (the TUI wires them from node:fs/promises and
144
+ // node:os). `load()` tolerates a missing or malformed file.
145
+ function createFileBookmarkStore({ file, readFile, writeFile, mkdir }) {
146
+ return {
147
+ async load() {
148
+ try {
149
+ const text = await readFile(file, "utf8")
150
+ const parsed = JSON.parse(text)
151
+ return parsed && typeof parsed === "object" ? parsed : {}
152
+ } catch (error) {
153
+ return {}
154
+ }
155
+ },
156
+ async save(map) {
157
+ try {
158
+ await mkdir?.(file.replace(/\/[^/]*$/, ""), { recursive: true })
159
+ await writeFile(file, `${JSON.stringify(map, null, 2)}\n`, "utf8")
160
+ } catch (error) {
161
+ // Persistence is best-effort; the in-memory map stays authoritative.
162
+ }
163
+ },
164
+ }
165
+ }
166
+
167
+ export {
168
+ createBookmarks,
169
+ createFileBookmarkStore,
170
+ createMemoryBookmarkStore,
171
+ isValidName,
172
+ normalizeEntry,
173
+ normalizeMap,
174
+ normalizeName,
175
+ }