fpf-cli 1.0.0
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.
- package/LICENSE +674 -0
- package/README.md +104 -0
- package/fpf +962 -0
- package/package.json +44 -0
package/fpf
ADDED
|
@@ -0,0 +1,962 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
SCRIPT_NAME="fpf"
|
|
6
|
+
TMP_ROOT="${TMPDIR:-/tmp}/fpf"
|
|
7
|
+
HELP_FILE="${TMP_ROOT}/help"
|
|
8
|
+
KBINDS_FILE="${TMP_ROOT}/keybinds"
|
|
9
|
+
|
|
10
|
+
ACTION="search"
|
|
11
|
+
MANAGER_OVERRIDE=""
|
|
12
|
+
declare -a QUERY_PARTS=()
|
|
13
|
+
|
|
14
|
+
log() {
|
|
15
|
+
printf "%s\n" "$*" >&2
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
die() {
|
|
19
|
+
log "Error: $*"
|
|
20
|
+
exit 1
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
command_exists() {
|
|
24
|
+
command -v "$1" >/dev/null 2>&1
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
ensure_tmp_root() {
|
|
28
|
+
mkdir -p "${TMP_ROOT}"
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
run_as_root() {
|
|
32
|
+
if [[ "${EUID}" -eq 0 ]]; then
|
|
33
|
+
"$@"
|
|
34
|
+
return
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
if command_exists sudo; then
|
|
38
|
+
sudo "$@"
|
|
39
|
+
return
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
die "Root privileges are required for: $*"
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
manager_list() {
|
|
46
|
+
printf "%s\n" "apt dnf pacman zypper emerge brew snap flatpak npm bun"
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
manager_supported() {
|
|
50
|
+
local manager="$1"
|
|
51
|
+
case "${manager}" in
|
|
52
|
+
apt|dnf|pacman|zypper|emerge|brew|snap|flatpak|npm|bun)
|
|
53
|
+
return 0
|
|
54
|
+
;;
|
|
55
|
+
*)
|
|
56
|
+
return 1
|
|
57
|
+
;;
|
|
58
|
+
esac
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
manager_command_ready() {
|
|
62
|
+
local manager="$1"
|
|
63
|
+
case "${manager}" in
|
|
64
|
+
apt)
|
|
65
|
+
command_exists apt-cache && command_exists apt-get && command_exists dpkg-query
|
|
66
|
+
;;
|
|
67
|
+
dnf)
|
|
68
|
+
command_exists dnf
|
|
69
|
+
;;
|
|
70
|
+
pacman)
|
|
71
|
+
command_exists pacman
|
|
72
|
+
;;
|
|
73
|
+
zypper)
|
|
74
|
+
command_exists zypper
|
|
75
|
+
;;
|
|
76
|
+
emerge)
|
|
77
|
+
command_exists emerge
|
|
78
|
+
;;
|
|
79
|
+
brew)
|
|
80
|
+
command_exists brew
|
|
81
|
+
;;
|
|
82
|
+
snap)
|
|
83
|
+
command_exists snap
|
|
84
|
+
;;
|
|
85
|
+
flatpak)
|
|
86
|
+
command_exists flatpak
|
|
87
|
+
;;
|
|
88
|
+
npm)
|
|
89
|
+
command_exists npm
|
|
90
|
+
;;
|
|
91
|
+
bun)
|
|
92
|
+
command_exists bun
|
|
93
|
+
;;
|
|
94
|
+
*)
|
|
95
|
+
return 1
|
|
96
|
+
;;
|
|
97
|
+
esac
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
normalize_manager() {
|
|
101
|
+
printf "%s" "$1" | tr '[:upper:]' '[:lower:]'
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
os_release_field() {
|
|
105
|
+
local file_path="$1"
|
|
106
|
+
local field_name="$2"
|
|
107
|
+
|
|
108
|
+
awk -v key="${field_name}" '
|
|
109
|
+
index($0, key "=") == 1 {
|
|
110
|
+
value = substr($0, index($0, "=") + 1)
|
|
111
|
+
gsub(/^[[:space:]]+|[[:space:]]+$/, "", value)
|
|
112
|
+
if (value ~ /^".*"$/ || value ~ /^\047.*\047$/) {
|
|
113
|
+
value = substr(value, 2, length(value) - 2)
|
|
114
|
+
}
|
|
115
|
+
print tolower(value)
|
|
116
|
+
exit
|
|
117
|
+
}
|
|
118
|
+
' "${file_path}"
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
manager_label() {
|
|
122
|
+
local manager="$1"
|
|
123
|
+
case "${manager}" in
|
|
124
|
+
apt) printf "APT" ;;
|
|
125
|
+
dnf) printf "DNF" ;;
|
|
126
|
+
pacman) printf "Pacman" ;;
|
|
127
|
+
zypper) printf "Zypper" ;;
|
|
128
|
+
emerge) printf "Portage (emerge)" ;;
|
|
129
|
+
brew) printf "Homebrew" ;;
|
|
130
|
+
snap) printf "Snap" ;;
|
|
131
|
+
flatpak) printf "Flatpak" ;;
|
|
132
|
+
npm) printf "npm" ;;
|
|
133
|
+
bun) printf "bun" ;;
|
|
134
|
+
*) printf "%s" "${manager}" ;;
|
|
135
|
+
esac
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
detect_default_manager() {
|
|
139
|
+
local os
|
|
140
|
+
os="$(uname -s)"
|
|
141
|
+
|
|
142
|
+
if [[ "${os}" == "Darwin" ]]; then
|
|
143
|
+
if command_exists brew; then
|
|
144
|
+
printf "brew"
|
|
145
|
+
return
|
|
146
|
+
fi
|
|
147
|
+
fi
|
|
148
|
+
|
|
149
|
+
if [[ "${os}" == "Linux" ]]; then
|
|
150
|
+
local distro_id=""
|
|
151
|
+
local distro_like=""
|
|
152
|
+
local os_release_file="${FPF_OS_RELEASE_FILE:-}"
|
|
153
|
+
|
|
154
|
+
if [[ -n "${os_release_file}" && ! -r "${os_release_file}" ]]; then
|
|
155
|
+
die "FPF_OS_RELEASE_FILE is set but not readable: ${os_release_file}"
|
|
156
|
+
fi
|
|
157
|
+
|
|
158
|
+
if [[ -z "${os_release_file}" && -r /etc/os-release ]]; then
|
|
159
|
+
os_release_file="/etc/os-release"
|
|
160
|
+
elif [[ -z "${os_release_file}" && -r /usr/lib/os-release ]]; then
|
|
161
|
+
os_release_file="/usr/lib/os-release"
|
|
162
|
+
fi
|
|
163
|
+
|
|
164
|
+
if [[ -n "${os_release_file}" ]]; then
|
|
165
|
+
distro_id="$(os_release_field "${os_release_file}" "ID")"
|
|
166
|
+
distro_like="$(os_release_field "${os_release_file}" "ID_LIKE")"
|
|
167
|
+
fi
|
|
168
|
+
|
|
169
|
+
case "${distro_id} ${distro_like}" in
|
|
170
|
+
*arch*|*manjaro*)
|
|
171
|
+
if command_exists pacman; then printf "pacman"; return; fi
|
|
172
|
+
;;
|
|
173
|
+
*ubuntu*|*debian*|*linuxmint*|*pop*|*elementary*)
|
|
174
|
+
if command_exists apt-get; then printf "apt"; return; fi
|
|
175
|
+
;;
|
|
176
|
+
*fedora*|*rhel*|*centos*|*rocky*|*alma*)
|
|
177
|
+
if command_exists dnf; then printf "dnf"; return; fi
|
|
178
|
+
;;
|
|
179
|
+
*opensuse*|*suse*|*sles*)
|
|
180
|
+
if command_exists zypper; then printf "zypper"; return; fi
|
|
181
|
+
;;
|
|
182
|
+
*gentoo*)
|
|
183
|
+
if command_exists emerge; then printf "emerge"; return; fi
|
|
184
|
+
;;
|
|
185
|
+
esac
|
|
186
|
+
|
|
187
|
+
if command_exists apt-get; then printf "apt"; return; fi
|
|
188
|
+
if command_exists dnf; then printf "dnf"; return; fi
|
|
189
|
+
if command_exists pacman; then printf "pacman"; return; fi
|
|
190
|
+
if command_exists zypper; then printf "zypper"; return; fi
|
|
191
|
+
if command_exists emerge; then printf "emerge"; return; fi
|
|
192
|
+
if command_exists snap; then printf "snap"; return; fi
|
|
193
|
+
if command_exists flatpak; then printf "flatpak"; return; fi
|
|
194
|
+
if command_exists npm; then printf "npm"; return; fi
|
|
195
|
+
if command_exists bun; then printf "bun"; return; fi
|
|
196
|
+
fi
|
|
197
|
+
|
|
198
|
+
if command_exists brew; then printf "brew"; return; fi
|
|
199
|
+
if command_exists npm; then printf "npm"; return; fi
|
|
200
|
+
if command_exists bun; then printf "bun"; return; fi
|
|
201
|
+
|
|
202
|
+
die "Unable to auto-detect a supported package manager. Use --manager."
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
build_help_file() {
|
|
206
|
+
local default_manager="$1"
|
|
207
|
+
|
|
208
|
+
cat >"${HELP_FILE}" <<EOF
|
|
209
|
+
${SCRIPT_NAME} - ultimate fuzzy package finder
|
|
210
|
+
|
|
211
|
+
Syntax:
|
|
212
|
+
${SCRIPT_NAME} [manager option] [action option] [query]
|
|
213
|
+
${SCRIPT_NAME} -m|--manager <name> [action option] [query]
|
|
214
|
+
|
|
215
|
+
Default behavior:
|
|
216
|
+
Fuzzy-search available packages and install selected items.
|
|
217
|
+
|
|
218
|
+
Detected default manager:
|
|
219
|
+
$(manager_label "${default_manager}") (${default_manager})
|
|
220
|
+
|
|
221
|
+
Action options:
|
|
222
|
+
-l, --list-installed Fuzzy-search installed packages and show details
|
|
223
|
+
-R, --remove Fuzzy-search installed packages and remove selected
|
|
224
|
+
-U, --update Run manager update/upgrade flow
|
|
225
|
+
-h, --help Show this help
|
|
226
|
+
|
|
227
|
+
Manager options (one or two-letter style):
|
|
228
|
+
-ap, --apt Use APT
|
|
229
|
+
-dn, --dnf Use DNF
|
|
230
|
+
-pm, --pacman Use Pacman
|
|
231
|
+
-zy, --zypper Use Zypper
|
|
232
|
+
-em, --emerge Use Portage (emerge)
|
|
233
|
+
-br, --brew Use Homebrew
|
|
234
|
+
-sn, --snap Use Snap
|
|
235
|
+
-fp, --flatpak Use Flatpak
|
|
236
|
+
-np, --npm Use npm (global packages)
|
|
237
|
+
-bn, --bun Use bun (global packages)
|
|
238
|
+
-ad, --auto Force auto-detection mode
|
|
239
|
+
|
|
240
|
+
Examples:
|
|
241
|
+
${SCRIPT_NAME} docker
|
|
242
|
+
${SCRIPT_NAME} -dn nginx
|
|
243
|
+
${SCRIPT_NAME} -ap -l openssl
|
|
244
|
+
${SCRIPT_NAME} -br -R wget
|
|
245
|
+
${SCRIPT_NAME} -sn firefox
|
|
246
|
+
${SCRIPT_NAME} -fp org.gimp.GIMP
|
|
247
|
+
${SCRIPT_NAME} -np eslint
|
|
248
|
+
${SCRIPT_NAME} -m apt ripgrep
|
|
249
|
+
|
|
250
|
+
Supported managers:
|
|
251
|
+
$(manager_list)
|
|
252
|
+
EOF
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
build_keybind_file() {
|
|
256
|
+
cat >"${KBINDS_FILE}" <<'EOF'
|
|
257
|
+
Keybinds:
|
|
258
|
+
|
|
259
|
+
ctrl-h Show help in preview pane
|
|
260
|
+
ctrl-k Show keybinds in preview pane
|
|
261
|
+
ctrl-/ Toggle preview pane
|
|
262
|
+
ctrl-n Move to next selected package
|
|
263
|
+
ctrl-b Move to previous selected package
|
|
264
|
+
EOF
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
print_help() {
|
|
268
|
+
cat "${HELP_FILE}"
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
parse_args() {
|
|
272
|
+
while (($#)); do
|
|
273
|
+
case "$1" in
|
|
274
|
+
-h|--help)
|
|
275
|
+
ACTION="help"
|
|
276
|
+
;;
|
|
277
|
+
-l|--list-installed)
|
|
278
|
+
ACTION="list"
|
|
279
|
+
;;
|
|
280
|
+
-R|--remove)
|
|
281
|
+
ACTION="remove"
|
|
282
|
+
;;
|
|
283
|
+
-U|--update)
|
|
284
|
+
ACTION="update"
|
|
285
|
+
;;
|
|
286
|
+
-ap|--apt)
|
|
287
|
+
MANAGER_OVERRIDE="apt"
|
|
288
|
+
;;
|
|
289
|
+
-dn|--dnf)
|
|
290
|
+
MANAGER_OVERRIDE="dnf"
|
|
291
|
+
;;
|
|
292
|
+
-pm|--pacman)
|
|
293
|
+
MANAGER_OVERRIDE="pacman"
|
|
294
|
+
;;
|
|
295
|
+
-zy|--zypper)
|
|
296
|
+
MANAGER_OVERRIDE="zypper"
|
|
297
|
+
;;
|
|
298
|
+
-em|--emerge)
|
|
299
|
+
MANAGER_OVERRIDE="emerge"
|
|
300
|
+
;;
|
|
301
|
+
-br|--brew)
|
|
302
|
+
MANAGER_OVERRIDE="brew"
|
|
303
|
+
;;
|
|
304
|
+
-sn|--snap)
|
|
305
|
+
MANAGER_OVERRIDE="snap"
|
|
306
|
+
;;
|
|
307
|
+
-fp|--flatpak)
|
|
308
|
+
MANAGER_OVERRIDE="flatpak"
|
|
309
|
+
;;
|
|
310
|
+
-np|--npm)
|
|
311
|
+
MANAGER_OVERRIDE="npm"
|
|
312
|
+
;;
|
|
313
|
+
-bn|--bun)
|
|
314
|
+
MANAGER_OVERRIDE="bun"
|
|
315
|
+
;;
|
|
316
|
+
-ad|--auto)
|
|
317
|
+
MANAGER_OVERRIDE=""
|
|
318
|
+
;;
|
|
319
|
+
-m|--manager)
|
|
320
|
+
shift
|
|
321
|
+
[[ $# -gt 0 ]] || die "Missing value for --manager"
|
|
322
|
+
MANAGER_OVERRIDE="$(normalize_manager "$1")"
|
|
323
|
+
;;
|
|
324
|
+
--)
|
|
325
|
+
shift
|
|
326
|
+
while (($#)); do
|
|
327
|
+
QUERY_PARTS+=("$1")
|
|
328
|
+
shift
|
|
329
|
+
done
|
|
330
|
+
break
|
|
331
|
+
;;
|
|
332
|
+
-*)
|
|
333
|
+
die "Invalid option: $1"
|
|
334
|
+
;;
|
|
335
|
+
*)
|
|
336
|
+
QUERY_PARTS+=("$1")
|
|
337
|
+
;;
|
|
338
|
+
esac
|
|
339
|
+
shift
|
|
340
|
+
done
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
join_query() {
|
|
344
|
+
local query=""
|
|
345
|
+
local part
|
|
346
|
+
|
|
347
|
+
if (( ${#QUERY_PARTS[@]} == 0 )); then
|
|
348
|
+
printf ""
|
|
349
|
+
return
|
|
350
|
+
fi
|
|
351
|
+
|
|
352
|
+
for part in "${QUERY_PARTS[@]}"; do
|
|
353
|
+
if [[ -z "${query}" ]]; then
|
|
354
|
+
query="${part}"
|
|
355
|
+
else
|
|
356
|
+
query+=" ${part}"
|
|
357
|
+
fi
|
|
358
|
+
done
|
|
359
|
+
printf "%s" "${query}"
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
manager_search_entries() {
|
|
363
|
+
local manager="$1"
|
|
364
|
+
local query="$2"
|
|
365
|
+
local effective_query="${query}"
|
|
366
|
+
|
|
367
|
+
if [[ -z "${effective_query}" ]]; then
|
|
368
|
+
case "${manager}" in
|
|
369
|
+
apt|dnf|pacman|zypper|emerge|snap|flatpak|npm|bun)
|
|
370
|
+
effective_query="a"
|
|
371
|
+
;;
|
|
372
|
+
brew)
|
|
373
|
+
effective_query=""
|
|
374
|
+
;;
|
|
375
|
+
esac
|
|
376
|
+
fi
|
|
377
|
+
|
|
378
|
+
case "${manager}" in
|
|
379
|
+
apt)
|
|
380
|
+
apt-cache search -- "${effective_query}" 2>/dev/null |
|
|
381
|
+
awk -F' - ' '{ name=$1; desc=$2; gsub(/^[[:space:]]+|[[:space:]]+$/, "", name); if (desc == "") desc="-"; print name "\t" desc }'
|
|
382
|
+
;;
|
|
383
|
+
dnf)
|
|
384
|
+
local pattern="*"
|
|
385
|
+
if [[ -n "${effective_query}" ]]; then
|
|
386
|
+
pattern="*${effective_query}*"
|
|
387
|
+
fi
|
|
388
|
+
dnf -q list available "${pattern}" 2>/dev/null |
|
|
389
|
+
awk 'NR > 1 && $1 !~ /^(Available|Last|Installed)/ { name=$1; sub(/\.[^.]+$/, "", name); print name "\t" $2 }'
|
|
390
|
+
;;
|
|
391
|
+
pacman)
|
|
392
|
+
pacman -Ss -- "${effective_query}" 2>/dev/null |
|
|
393
|
+
awk '
|
|
394
|
+
NR % 2 == 1 {
|
|
395
|
+
split($1, parts, "/")
|
|
396
|
+
pkg = parts[2]
|
|
397
|
+
next
|
|
398
|
+
}
|
|
399
|
+
NR % 2 == 0 {
|
|
400
|
+
line = $0
|
|
401
|
+
sub(/^[[:space:]]+/, "", line)
|
|
402
|
+
if (pkg != "") print pkg "\t" line
|
|
403
|
+
}
|
|
404
|
+
'
|
|
405
|
+
;;
|
|
406
|
+
zypper)
|
|
407
|
+
zypper --non-interactive --quiet search --details --type package "${effective_query}" 2>/dev/null |
|
|
408
|
+
awk -F'|' '
|
|
409
|
+
/^[[:space:]]*[ivp ][[:space:]]*\|/ {
|
|
410
|
+
name=$3
|
|
411
|
+
ver=$5
|
|
412
|
+
repo=$7
|
|
413
|
+
gsub(/^[[:space:]]+|[[:space:]]+$/, "", name)
|
|
414
|
+
gsub(/^[[:space:]]+|[[:space:]]+$/, "", ver)
|
|
415
|
+
gsub(/^[[:space:]]+|[[:space:]]+$/, "", repo)
|
|
416
|
+
if (name != "") print name "\tversion " ver " from " repo
|
|
417
|
+
}
|
|
418
|
+
'
|
|
419
|
+
;;
|
|
420
|
+
emerge)
|
|
421
|
+
emerge --searchdesc --color=n "${effective_query}" 2>/dev/null |
|
|
422
|
+
awk '
|
|
423
|
+
/^\* / {
|
|
424
|
+
atom=$2
|
|
425
|
+
desc="-"
|
|
426
|
+
next
|
|
427
|
+
}
|
|
428
|
+
/^[[:space:]]+Description:/ {
|
|
429
|
+
line=$0
|
|
430
|
+
sub(/^[[:space:]]+Description:[[:space:]]*/, "", line)
|
|
431
|
+
desc=line
|
|
432
|
+
if (atom != "") print atom "\t" desc
|
|
433
|
+
atom=""
|
|
434
|
+
}
|
|
435
|
+
'
|
|
436
|
+
;;
|
|
437
|
+
brew)
|
|
438
|
+
brew search "${effective_query}" 2>/dev/null |
|
|
439
|
+
awk 'NF > 0 { print $1 "\t-" }'
|
|
440
|
+
;;
|
|
441
|
+
snap)
|
|
442
|
+
snap find "${effective_query}" 2>/dev/null |
|
|
443
|
+
awk '
|
|
444
|
+
NR == 1 { next }
|
|
445
|
+
NF > 0 {
|
|
446
|
+
name=$1
|
|
447
|
+
$1=""
|
|
448
|
+
sub(/^[[:space:]]+/, "", $0)
|
|
449
|
+
if ($0 == "") $0 = "-"
|
|
450
|
+
print name "\t" $0
|
|
451
|
+
}
|
|
452
|
+
'
|
|
453
|
+
;;
|
|
454
|
+
flatpak)
|
|
455
|
+
if [[ -z "${effective_query}" ]]; then
|
|
456
|
+
{
|
|
457
|
+
flatpak remote-ls --app --columns=application,description flathub 2>/dev/null ||
|
|
458
|
+
flatpak remote-ls --app --columns=application,description 2>/dev/null
|
|
459
|
+
} |
|
|
460
|
+
awk 'NR > 1 { name=$1; $1=""; sub(/^[[:space:]]+/, "", $0); if ($0 == "") $0="-"; print name "\t" $0 }'
|
|
461
|
+
else
|
|
462
|
+
{
|
|
463
|
+
flatpak search --columns=application,description "${effective_query}" 2>/dev/null ||
|
|
464
|
+
flatpak search "${effective_query}" 2>/dev/null
|
|
465
|
+
} |
|
|
466
|
+
awk 'NR > 1 { name=$1; $1=""; sub(/^[[:space:]]+/, "", $0); if ($0 == "") $0="-"; print name "\t" $0 }'
|
|
467
|
+
fi
|
|
468
|
+
;;
|
|
469
|
+
npm)
|
|
470
|
+
npm search "${effective_query}" --searchlimit=500 --parseable 2>/dev/null |
|
|
471
|
+
awk -F'\t' 'NF >= 2 { print $1 "\t" $2 }'
|
|
472
|
+
;;
|
|
473
|
+
bun)
|
|
474
|
+
{
|
|
475
|
+
if bun search "${effective_query}" >/dev/null 2>&1; then
|
|
476
|
+
bun search "${effective_query}" 2>/dev/null |
|
|
477
|
+
awk 'NR > 1 && NF > 0 { name=$1; $1=""; sub(/^[[:space:]]+/, "", $0); if ($0 == "") $0="-"; print name "\t" $0 }'
|
|
478
|
+
fi
|
|
479
|
+
if command_exists npm; then
|
|
480
|
+
npm search "${effective_query}" --searchlimit=500 --parseable 2>/dev/null |
|
|
481
|
+
awk -F'\t' 'NF >= 2 { print $1 "\t" $2 }'
|
|
482
|
+
fi
|
|
483
|
+
} || true
|
|
484
|
+
;;
|
|
485
|
+
esac | awk -F'\t' 'NF >= 1 { if ($2 == "") $2 = "-"; print $1 "\t" $2 }' | sort -u || true
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
manager_installed_entries() {
|
|
489
|
+
local manager="$1"
|
|
490
|
+
|
|
491
|
+
case "${manager}" in
|
|
492
|
+
apt)
|
|
493
|
+
dpkg-query -W -f='${binary:Package}\t${Version}\n' 2>/dev/null
|
|
494
|
+
;;
|
|
495
|
+
dnf)
|
|
496
|
+
dnf -q list installed 2>/dev/null |
|
|
497
|
+
awk 'NR > 1 && $1 !~ /^(Installed|Last)/ { name=$1; sub(/\.[^.]+$/, "", name); print name "\t" $2 }'
|
|
498
|
+
;;
|
|
499
|
+
pacman)
|
|
500
|
+
pacman -Q 2>/dev/null |
|
|
501
|
+
awk '{ print $1 "\t" $2 }'
|
|
502
|
+
;;
|
|
503
|
+
zypper)
|
|
504
|
+
zypper --non-interactive --quiet search --installed-only --details --type package 2>/dev/null |
|
|
505
|
+
awk -F'|' '
|
|
506
|
+
/^[[:space:]]*i[[:space:]]*\|/ {
|
|
507
|
+
name=$3
|
|
508
|
+
ver=$5
|
|
509
|
+
gsub(/^[[:space:]]+|[[:space:]]+$/, "", name)
|
|
510
|
+
gsub(/^[[:space:]]+|[[:space:]]+$/, "", ver)
|
|
511
|
+
if (name != "") print name "\t" ver
|
|
512
|
+
}
|
|
513
|
+
'
|
|
514
|
+
;;
|
|
515
|
+
emerge)
|
|
516
|
+
if command_exists qlist; then
|
|
517
|
+
qlist -ICv 2>/dev/null |
|
|
518
|
+
awk '{ print $1 "\tinstalled" }'
|
|
519
|
+
else
|
|
520
|
+
local pkg_dir
|
|
521
|
+
for pkg_dir in /var/db/pkg/*/*; do
|
|
522
|
+
[[ -d "${pkg_dir}" ]] || continue
|
|
523
|
+
printf "%s\tinstalled\n" "$(basename "${pkg_dir}")"
|
|
524
|
+
done
|
|
525
|
+
fi
|
|
526
|
+
;;
|
|
527
|
+
brew)
|
|
528
|
+
brew list --versions 2>/dev/null |
|
|
529
|
+
awk '{ name=$1; $1=""; sub(/^[[:space:]]+/, "", $0); if ($0 == "") $0="installed"; print name "\t" $0 }'
|
|
530
|
+
;;
|
|
531
|
+
snap)
|
|
532
|
+
snap list 2>/dev/null |
|
|
533
|
+
awk 'NR > 1 { print $1 "\t" $2 }'
|
|
534
|
+
;;
|
|
535
|
+
flatpak)
|
|
536
|
+
flatpak list --app --columns=application,version 2>/dev/null |
|
|
537
|
+
awk 'NR > 1 { print $1 "\t" $2 }'
|
|
538
|
+
;;
|
|
539
|
+
npm)
|
|
540
|
+
npm ls -g --depth=0 --parseable 2>/dev/null |
|
|
541
|
+
awk -F'/' 'NR > 1 { print $NF "\tglobal" }'
|
|
542
|
+
;;
|
|
543
|
+
bun)
|
|
544
|
+
{
|
|
545
|
+
if bun pm ls --global >/dev/null 2>&1; then
|
|
546
|
+
bun pm ls --global 2>/dev/null |
|
|
547
|
+
awk 'NR > 1 && NF > 0 { print $1 "\tglobal" }'
|
|
548
|
+
elif bun pm ls >/dev/null 2>&1; then
|
|
549
|
+
bun pm ls 2>/dev/null |
|
|
550
|
+
awk 'NR > 1 && NF > 0 { print $1 "\tglobal" }'
|
|
551
|
+
fi
|
|
552
|
+
if command_exists npm; then
|
|
553
|
+
npm ls -g --depth=0 --parseable 2>/dev/null |
|
|
554
|
+
awk -F'/' 'NR > 1 { print $NF "\tglobal" }'
|
|
555
|
+
fi
|
|
556
|
+
} || true
|
|
557
|
+
;;
|
|
558
|
+
esac | sort -u || true
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
manager_installed_names() {
|
|
562
|
+
local manager="$1"
|
|
563
|
+
manager_installed_entries "${manager}" | awk -F'\t' 'NF > 0 { print $1 }'
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
mark_installed_packages() {
|
|
567
|
+
local manager="$1"
|
|
568
|
+
local source_file="$2"
|
|
569
|
+
local output_file="$3"
|
|
570
|
+
local installed_file
|
|
571
|
+
|
|
572
|
+
installed_file="$(mktemp "${TMP_ROOT}/installed.XXXXXX")"
|
|
573
|
+
manager_installed_names "${manager}" >"${installed_file}" 2>/dev/null || true
|
|
574
|
+
|
|
575
|
+
awk -F'\t' '
|
|
576
|
+
FILENAME == ARGV[1] {
|
|
577
|
+
if ($1 != "") {
|
|
578
|
+
installed[$1] = 1
|
|
579
|
+
}
|
|
580
|
+
next
|
|
581
|
+
}
|
|
582
|
+
{
|
|
583
|
+
mark = (installed[$1] ? "* " : " ")
|
|
584
|
+
desc = $2
|
|
585
|
+
if (desc == "") desc = "-"
|
|
586
|
+
print $1 "\t" mark desc
|
|
587
|
+
}
|
|
588
|
+
' "${installed_file}" "${source_file}" >"${output_file}"
|
|
589
|
+
|
|
590
|
+
rm -f "${installed_file}"
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
manager_preview_command() {
|
|
594
|
+
local manager="$1"
|
|
595
|
+
|
|
596
|
+
case "${manager}" in
|
|
597
|
+
apt)
|
|
598
|
+
printf '%s' 'pkg={1}; apt-cache show "$pkg" 2>/dev/null; printf "\n"; dpkg -L "$pkg" 2>/dev/null'
|
|
599
|
+
;;
|
|
600
|
+
dnf)
|
|
601
|
+
printf '%s' 'pkg={1}; dnf info "$pkg" 2>/dev/null; printf "\n"; rpm -ql "$pkg" 2>/dev/null'
|
|
602
|
+
;;
|
|
603
|
+
pacman)
|
|
604
|
+
printf '%s' 'pkg={1}; pacman -Si "$pkg" 2>/dev/null; printf "\n"; pacman -Fl "$pkg" 2>/dev/null | awk "{print \$2}"'
|
|
605
|
+
;;
|
|
606
|
+
zypper)
|
|
607
|
+
printf '%s' 'pkg={1}; zypper --non-interactive info "$pkg" 2>/dev/null'
|
|
608
|
+
;;
|
|
609
|
+
emerge)
|
|
610
|
+
printf '%s' 'pkg={1}; emerge --search --color=n "$pkg" 2>/dev/null'
|
|
611
|
+
;;
|
|
612
|
+
brew)
|
|
613
|
+
printf '%s' 'pkg={1}; brew info "$pkg" 2>/dev/null'
|
|
614
|
+
;;
|
|
615
|
+
snap)
|
|
616
|
+
printf '%s' 'pkg={1}; snap info "$pkg" 2>/dev/null'
|
|
617
|
+
;;
|
|
618
|
+
flatpak)
|
|
619
|
+
printf '%s' 'pkg={1}; flatpak info "$pkg" 2>/dev/null || flatpak remote-info flathub "$pkg" 2>/dev/null'
|
|
620
|
+
;;
|
|
621
|
+
npm)
|
|
622
|
+
printf '%s' 'pkg={1}; npm view "$pkg" 2>/dev/null'
|
|
623
|
+
;;
|
|
624
|
+
bun)
|
|
625
|
+
printf '%s' 'pkg={1}; bun info "$pkg" 2>/dev/null || npm view "$pkg" 2>/dev/null'
|
|
626
|
+
;;
|
|
627
|
+
esac
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
manager_install() {
|
|
631
|
+
local manager="$1"
|
|
632
|
+
shift
|
|
633
|
+
|
|
634
|
+
case "${manager}" in
|
|
635
|
+
apt)
|
|
636
|
+
run_as_root apt-get install -y "$@"
|
|
637
|
+
;;
|
|
638
|
+
dnf)
|
|
639
|
+
run_as_root dnf install -y "$@"
|
|
640
|
+
;;
|
|
641
|
+
pacman)
|
|
642
|
+
run_as_root pacman -S --needed "$@"
|
|
643
|
+
;;
|
|
644
|
+
zypper)
|
|
645
|
+
run_as_root zypper --non-interactive install --auto-agree-with-licenses "$@"
|
|
646
|
+
;;
|
|
647
|
+
emerge)
|
|
648
|
+
run_as_root emerge --ask=n --verbose "$@"
|
|
649
|
+
;;
|
|
650
|
+
brew)
|
|
651
|
+
brew install "$@"
|
|
652
|
+
;;
|
|
653
|
+
snap)
|
|
654
|
+
local pkg
|
|
655
|
+
for pkg in "$@"; do
|
|
656
|
+
run_as_root snap install "${pkg}" 2>/dev/null || run_as_root snap install --classic "${pkg}"
|
|
657
|
+
done
|
|
658
|
+
;;
|
|
659
|
+
flatpak)
|
|
660
|
+
local pkg
|
|
661
|
+
for pkg in "$@"; do
|
|
662
|
+
flatpak install -y --user flathub "${pkg}" 2>/dev/null || flatpak install -y --user "${pkg}"
|
|
663
|
+
done
|
|
664
|
+
;;
|
|
665
|
+
npm)
|
|
666
|
+
npm install -g "$@"
|
|
667
|
+
;;
|
|
668
|
+
bun)
|
|
669
|
+
bun add -g "$@"
|
|
670
|
+
;;
|
|
671
|
+
esac
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
manager_remove() {
|
|
675
|
+
local manager="$1"
|
|
676
|
+
shift
|
|
677
|
+
|
|
678
|
+
case "${manager}" in
|
|
679
|
+
apt)
|
|
680
|
+
run_as_root apt-get remove -y "$@"
|
|
681
|
+
;;
|
|
682
|
+
dnf)
|
|
683
|
+
run_as_root dnf remove -y "$@"
|
|
684
|
+
;;
|
|
685
|
+
pacman)
|
|
686
|
+
run_as_root pacman -Rsn "$@"
|
|
687
|
+
;;
|
|
688
|
+
zypper)
|
|
689
|
+
run_as_root zypper --non-interactive remove "$@"
|
|
690
|
+
;;
|
|
691
|
+
emerge)
|
|
692
|
+
run_as_root emerge --ask=n --deselect "$@"
|
|
693
|
+
run_as_root emerge --ask=n --depclean "$@"
|
|
694
|
+
;;
|
|
695
|
+
brew)
|
|
696
|
+
brew uninstall "$@"
|
|
697
|
+
;;
|
|
698
|
+
snap)
|
|
699
|
+
run_as_root snap remove "$@"
|
|
700
|
+
;;
|
|
701
|
+
flatpak)
|
|
702
|
+
flatpak uninstall -y --user "$@"
|
|
703
|
+
;;
|
|
704
|
+
npm)
|
|
705
|
+
npm uninstall -g "$@"
|
|
706
|
+
;;
|
|
707
|
+
bun)
|
|
708
|
+
bun remove --global "$@" 2>/dev/null || bun remove "$@"
|
|
709
|
+
;;
|
|
710
|
+
esac
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
manager_show_info() {
|
|
714
|
+
local manager="$1"
|
|
715
|
+
local package="$2"
|
|
716
|
+
|
|
717
|
+
case "${manager}" in
|
|
718
|
+
apt)
|
|
719
|
+
cat <(apt-cache show "${package}" 2>/dev/null) <(printf "\n") <(dpkg -L "${package}" 2>/dev/null)
|
|
720
|
+
;;
|
|
721
|
+
dnf)
|
|
722
|
+
cat <(dnf info "${package}" 2>/dev/null) <(printf "\n") <(rpm -ql "${package}" 2>/dev/null)
|
|
723
|
+
;;
|
|
724
|
+
pacman)
|
|
725
|
+
cat <(pacman -Qi "${package}" 2>/dev/null || pacman -Si "${package}" 2>/dev/null) <(printf "\n") <(pacman -Ql "${package}" 2>/dev/null)
|
|
726
|
+
;;
|
|
727
|
+
zypper)
|
|
728
|
+
zypper --non-interactive info "${package}" 2>/dev/null
|
|
729
|
+
;;
|
|
730
|
+
emerge)
|
|
731
|
+
emerge --search --color=n "${package}" 2>/dev/null
|
|
732
|
+
;;
|
|
733
|
+
brew)
|
|
734
|
+
brew info "${package}" 2>/dev/null
|
|
735
|
+
;;
|
|
736
|
+
snap)
|
|
737
|
+
snap info "${package}" 2>/dev/null
|
|
738
|
+
;;
|
|
739
|
+
flatpak)
|
|
740
|
+
flatpak info "${package}" 2>/dev/null || flatpak remote-info flathub "${package}" 2>/dev/null
|
|
741
|
+
;;
|
|
742
|
+
npm)
|
|
743
|
+
npm view "${package}" 2>/dev/null
|
|
744
|
+
;;
|
|
745
|
+
bun)
|
|
746
|
+
bun info "${package}" 2>/dev/null || npm view "${package}" 2>/dev/null
|
|
747
|
+
;;
|
|
748
|
+
esac
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
manager_update() {
|
|
752
|
+
local manager="$1"
|
|
753
|
+
|
|
754
|
+
case "${manager}" in
|
|
755
|
+
apt)
|
|
756
|
+
run_as_root apt-get update
|
|
757
|
+
run_as_root apt-get upgrade -y
|
|
758
|
+
;;
|
|
759
|
+
dnf)
|
|
760
|
+
run_as_root dnf upgrade -y
|
|
761
|
+
;;
|
|
762
|
+
pacman)
|
|
763
|
+
run_as_root pacman -Syu
|
|
764
|
+
;;
|
|
765
|
+
zypper)
|
|
766
|
+
run_as_root zypper --non-interactive refresh
|
|
767
|
+
run_as_root zypper --non-interactive update
|
|
768
|
+
;;
|
|
769
|
+
emerge)
|
|
770
|
+
run_as_root emerge --sync
|
|
771
|
+
run_as_root emerge --ask=n --update --deep --newuse @world
|
|
772
|
+
;;
|
|
773
|
+
brew)
|
|
774
|
+
brew update
|
|
775
|
+
brew upgrade
|
|
776
|
+
;;
|
|
777
|
+
snap)
|
|
778
|
+
run_as_root snap refresh
|
|
779
|
+
;;
|
|
780
|
+
flatpak)
|
|
781
|
+
flatpak update -y --user
|
|
782
|
+
;;
|
|
783
|
+
npm)
|
|
784
|
+
npm update -g
|
|
785
|
+
;;
|
|
786
|
+
bun)
|
|
787
|
+
bun update --global 2>/dev/null || bun update
|
|
788
|
+
;;
|
|
789
|
+
esac
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
confirm_action() {
|
|
793
|
+
local prompt="$1"
|
|
794
|
+
local reply=""
|
|
795
|
+
|
|
796
|
+
printf "%s [y/N]: " "${prompt}" >&2
|
|
797
|
+
read -r reply || true
|
|
798
|
+
|
|
799
|
+
case "${reply}" in
|
|
800
|
+
y|Y|yes|YES)
|
|
801
|
+
return 0
|
|
802
|
+
;;
|
|
803
|
+
*)
|
|
804
|
+
return 1
|
|
805
|
+
;;
|
|
806
|
+
esac
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
run_fuzzy_selector() {
|
|
810
|
+
local manager="$1"
|
|
811
|
+
local query="$2"
|
|
812
|
+
local input_file="$3"
|
|
813
|
+
local header_line="$4"
|
|
814
|
+
local preview_cmd
|
|
815
|
+
|
|
816
|
+
preview_cmd="$(manager_preview_command "${manager}")"
|
|
817
|
+
|
|
818
|
+
fzf -q "${query}" -e -m \
|
|
819
|
+
--delimiter=$'\t' \
|
|
820
|
+
--with-nth=1,2 \
|
|
821
|
+
--preview="${preview_cmd}" \
|
|
822
|
+
--preview-window=55%:wrap:border-sharp \
|
|
823
|
+
--layout=reverse \
|
|
824
|
+
--marker='>>' \
|
|
825
|
+
--header="${header_line}" \
|
|
826
|
+
--info=hidden \
|
|
827
|
+
--margin="2%,1%,2%,1%" \
|
|
828
|
+
--cycle \
|
|
829
|
+
--bind=ctrl-k:preview:"cat ${KBINDS_FILE}" \
|
|
830
|
+
--bind=ctrl-h:preview:"cat ${HELP_FILE}" \
|
|
831
|
+
--bind='ctrl-/:change-preview-window(hidden|)' \
|
|
832
|
+
--bind=ctrl-n:next-selected,ctrl-b:prev-selected \
|
|
833
|
+
--bind='focus:transform-preview-label:echo package: {1}' \
|
|
834
|
+
<"${input_file}"
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
collect_selected_packages() {
|
|
838
|
+
local selected_lines="$1"
|
|
839
|
+
printf "%s\n" "${selected_lines}" | awk -F'\t' 'NF > 0 { print $1 }'
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
main() {
|
|
843
|
+
ensure_tmp_root
|
|
844
|
+
|
|
845
|
+
parse_args "$@"
|
|
846
|
+
|
|
847
|
+
local detected_manager
|
|
848
|
+
detected_manager="$(detect_default_manager)"
|
|
849
|
+
|
|
850
|
+
build_help_file "${detected_manager}"
|
|
851
|
+
build_keybind_file
|
|
852
|
+
|
|
853
|
+
if [[ "${ACTION}" == "help" ]]; then
|
|
854
|
+
print_help
|
|
855
|
+
exit 0
|
|
856
|
+
fi
|
|
857
|
+
|
|
858
|
+
local manager="${detected_manager}"
|
|
859
|
+
if [[ -n "${MANAGER_OVERRIDE}" ]]; then
|
|
860
|
+
manager="$(normalize_manager "${MANAGER_OVERRIDE}")"
|
|
861
|
+
fi
|
|
862
|
+
|
|
863
|
+
manager_supported "${manager}" || die "Unsupported manager: ${manager}"
|
|
864
|
+
manager_command_ready "${manager}" || die "Manager command(s) for '${manager}' not found on this system"
|
|
865
|
+
|
|
866
|
+
local query
|
|
867
|
+
query="$(join_query)"
|
|
868
|
+
|
|
869
|
+
if [[ "${ACTION}" == "update" ]]; then
|
|
870
|
+
log "Using manager: $(manager_label "${manager}")"
|
|
871
|
+
if confirm_action "Run update/upgrade for ${manager}?"; then
|
|
872
|
+
manager_update "${manager}"
|
|
873
|
+
else
|
|
874
|
+
log "Update canceled"
|
|
875
|
+
fi
|
|
876
|
+
exit 0
|
|
877
|
+
fi
|
|
878
|
+
|
|
879
|
+
command_exists fzf || die "fzf is required"
|
|
880
|
+
|
|
881
|
+
local source_file
|
|
882
|
+
local display_file
|
|
883
|
+
source_file="$(mktemp "${TMP_ROOT}/source.XXXXXX")"
|
|
884
|
+
display_file="$(mktemp "${TMP_ROOT}/display.XXXXXX")"
|
|
885
|
+
|
|
886
|
+
if [[ "${ACTION}" == "list" || "${ACTION}" == "remove" ]]; then
|
|
887
|
+
manager_installed_entries "${manager}" >"${source_file}"
|
|
888
|
+
cp "${source_file}" "${display_file}"
|
|
889
|
+
else
|
|
890
|
+
manager_search_entries "${manager}" "${query}" >"${source_file}"
|
|
891
|
+
mark_installed_packages "${manager}" "${source_file}" "${display_file}"
|
|
892
|
+
fi
|
|
893
|
+
|
|
894
|
+
if [[ ! -s "${display_file}" ]]; then
|
|
895
|
+
rm -f "${source_file}" "${display_file}"
|
|
896
|
+
die "No packages found for manager '${manager}' and query '${query}'"
|
|
897
|
+
fi
|
|
898
|
+
|
|
899
|
+
local header
|
|
900
|
+
case "${ACTION}" in
|
|
901
|
+
search)
|
|
902
|
+
header="Select package(s) to install with ${manager} (TAB to multi-select)"
|
|
903
|
+
;;
|
|
904
|
+
list)
|
|
905
|
+
header="Select installed package(s) to inspect"
|
|
906
|
+
;;
|
|
907
|
+
remove)
|
|
908
|
+
header="Select installed package(s) to remove"
|
|
909
|
+
;;
|
|
910
|
+
*)
|
|
911
|
+
header="Select package(s)"
|
|
912
|
+
;;
|
|
913
|
+
esac
|
|
914
|
+
|
|
915
|
+
local selected
|
|
916
|
+
selected="$(run_fuzzy_selector "${manager}" "${query}" "${display_file}" "${header}" || true)"
|
|
917
|
+
|
|
918
|
+
rm -f "${source_file}" "${display_file}"
|
|
919
|
+
|
|
920
|
+
if [[ -z "${selected}" ]]; then
|
|
921
|
+
log "No package selected"
|
|
922
|
+
exit 0
|
|
923
|
+
fi
|
|
924
|
+
|
|
925
|
+
local packages=()
|
|
926
|
+
local selected_pkg
|
|
927
|
+
while IFS= read -r selected_pkg; do
|
|
928
|
+
[[ -n "${selected_pkg}" ]] || continue
|
|
929
|
+
packages+=("${selected_pkg}")
|
|
930
|
+
done < <(collect_selected_packages "${selected}")
|
|
931
|
+
|
|
932
|
+
if [[ "${#packages[@]}" -eq 0 ]]; then
|
|
933
|
+
log "No package selected"
|
|
934
|
+
exit 0
|
|
935
|
+
fi
|
|
936
|
+
|
|
937
|
+
case "${ACTION}" in
|
|
938
|
+
search)
|
|
939
|
+
if confirm_action "Install ${#packages[@]} package(s) with ${manager}?"; then
|
|
940
|
+
manager_install "${manager}" "${packages[@]}"
|
|
941
|
+
else
|
|
942
|
+
log "Install canceled"
|
|
943
|
+
fi
|
|
944
|
+
;;
|
|
945
|
+
remove)
|
|
946
|
+
if confirm_action "Remove ${#packages[@]} package(s) with ${manager}?"; then
|
|
947
|
+
manager_remove "${manager}" "${packages[@]}"
|
|
948
|
+
else
|
|
949
|
+
log "Remove canceled"
|
|
950
|
+
fi
|
|
951
|
+
;;
|
|
952
|
+
list)
|
|
953
|
+
local pkg
|
|
954
|
+
for pkg in "${packages[@]}"; do
|
|
955
|
+
printf "\n=== %s (%s) ===\n" "${pkg}" "$(manager_label "${manager}")"
|
|
956
|
+
manager_show_info "${manager}" "${pkg}" || true
|
|
957
|
+
done
|
|
958
|
+
;;
|
|
959
|
+
esac
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
main "$@"
|