fpf-cli 1.6.30 → 1.6.32

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 (4) hide show
  1. package/README.md +3 -1
  2. package/fpf +285 -21
  3. package/fpf.png +0 -0
  4. package/package.json +3 -2
package/README.md CHANGED
@@ -74,6 +74,7 @@ Live reload is enabled by default, with a minimum query length and debounce to r
74
74
  - `-R, --remove` remove selected packages
75
75
  - `-U, --update` run update/upgrade flow
76
76
  - `--refresh` refresh package catalogs only
77
+ - `-y, --yes` skip confirmation prompts
77
78
  - `-v, --version` print version and exit
78
79
  - `-h, --help` show help
79
80
 
@@ -90,9 +91,10 @@ Installed packages are marked with `*` in the result list.
90
91
  ## Notes
91
92
 
92
93
  - Requires: `bash` + `fzf`
93
- - If `fzf` is missing, `fpf` auto-installs it using a compatible detected manager.
94
+ - If `fzf` is missing, `fpf` auto-installs it using a compatible detected manager, then falls back to a release binary download if manager bootstrap fails.
94
95
  - Root managers (`apt`, `dnf`, `pacman`, `zypper`, `emerge`, `snap`) use `sudo` when needed.
95
96
  - If Flatpak is detected and Flathub is missing, `fpf` attempts `flatpak remote-add --if-not-exists --user flathub ...` automatically.
97
+ - Set `FPF_ASSUME_YES=1` to bypass confirmation prompts in non-interactive flows.
96
98
  - `FPF_DYNAMIC_RELOAD`: `always` (default), `single`, or `never`
97
99
  - Live reload uses `change:reload` by default for reliability (`ctrl-r` uses the same reload command).
98
100
  - Set `FPF_DYNAMIC_RELOAD_TRANSPORT=ipc` to opt into `--listen` + IPC query notifications on supported `fzf` builds.
package/fpf CHANGED
@@ -3,7 +3,7 @@
3
3
  set -euo pipefail
4
4
 
5
5
  SCRIPT_NAME="fpf"
6
- SCRIPT_VERSION="1.6.30"
6
+ SCRIPT_VERSION="1.6.32"
7
7
  TMP_ROOT="${TMPDIR:-/tmp}/fpf"
8
8
  SESSION_TMP_ROOT=""
9
9
  HELP_FILE=""
@@ -15,6 +15,7 @@ ACTION="search"
15
15
  MANAGER_OVERRIDE=""
16
16
  IPC_MANAGER_OVERRIDE=""
17
17
  IPC_FALLBACK_FILE=""
18
+ ASSUME_YES="0"
18
19
  declare -a QUERY_PARTS=()
19
20
  FPF_LIBRARIES_LOADED="0"
20
21
 
@@ -77,6 +78,32 @@ load_fpf_libraries() {
77
78
  FPF_LIBRARIES_LOADED="1"
78
79
  }
79
80
 
81
+ fzf_command_available() {
82
+ if [[ "${FPF_TEST_FORCE_FZF_MISSING:-0}" == "1" ]]; then
83
+ if [[ -n "${FPF_TEST_MOCK_BIN:-}" && -x "${FPF_TEST_MOCK_BIN}/fzf" ]]; then
84
+ return 0
85
+ fi
86
+ return 1
87
+ fi
88
+
89
+ command_exists fzf
90
+ }
91
+
92
+ assume_yes_enabled() {
93
+ if [[ "${ASSUME_YES}" == "1" ]]; then
94
+ return 0
95
+ fi
96
+
97
+ case "${FPF_ASSUME_YES:-0}" in
98
+ 1|true|TRUE|yes|YES|on|ON)
99
+ return 0
100
+ ;;
101
+ *)
102
+ return 1
103
+ ;;
104
+ esac
105
+ }
106
+
80
107
  ensure_tmp_root() {
81
108
  mkdir -p "${TMP_ROOT}"
82
109
  }
@@ -189,30 +216,127 @@ cache_catalog_key() {
189
216
  printf "catalog/%s.tsv" "${manager}"
190
217
  }
191
218
 
219
+ catalog_fixture_checksum() {
220
+ local fixture_name="$1"
221
+ local fixture_path=""
222
+
223
+ if [[ "${FPF_TEST_FIXTURES:-0}" != "1" ]]; then
224
+ return 1
225
+ fi
226
+
227
+ if [[ -z "${FPF_TEST_FIXTURE_DIR:-}" ]]; then
228
+ return 1
229
+ fi
230
+
231
+ fixture_path="${FPF_TEST_FIXTURE_DIR}/${fixture_name}"
232
+ [[ -r "${fixture_path}" ]] || return 1
233
+ cksum "${fixture_path}" | awk '{ print $1 }'
234
+ }
235
+
236
+ apt_catalog_state_token() {
237
+ local fixture_checksum=""
238
+ local lists_dir="/var/lib/apt/lists"
239
+ local listing=""
240
+ local listing_checksum=""
241
+ local policy_checksum=""
242
+
243
+ fixture_checksum="$(catalog_fixture_checksum "apt-dumpavail.txt" 2>/dev/null || true)"
244
+ if [[ -n "${fixture_checksum}" ]]; then
245
+ printf "fixture=%s" "${fixture_checksum}"
246
+ return
247
+ fi
248
+
249
+ if [[ -d "${lists_dir}" ]]; then
250
+ listing="$(find "${lists_dir}" -maxdepth 1 -type f \( -name '*_Packages' -o -name '*_Sources' -o -name '*InRelease' -o -name '*Release' \) -printf '%f|%s|%T@\n' 2>/dev/null | LC_ALL=C sort || true)"
251
+ if [[ -n "${listing}" ]]; then
252
+ listing_checksum="$(printf "%s" "${listing}" | cksum | awk '{ print $1 }')"
253
+ printf "lists=%s" "${listing_checksum}"
254
+ return
255
+ fi
256
+ fi
257
+
258
+ if command_exists apt-cache; then
259
+ policy_checksum="$(apt-cache policy 2>/dev/null | cksum | awk '{ print $1 }' || true)"
260
+ if [[ -n "${policy_checksum}" ]]; then
261
+ printf "policy=%s" "${policy_checksum}"
262
+ return
263
+ fi
264
+ fi
265
+
266
+ printf "state=unknown"
267
+ }
268
+
269
+ brew_catalog_state_token() {
270
+ local formula_fixture_checksum=""
271
+ local cask_fixture_checksum=""
272
+ local formula_repo=""
273
+ local cask_repo=""
274
+ local formula_head=""
275
+ local cask_head=""
276
+ local brew_version=""
277
+
278
+ formula_fixture_checksum="$(catalog_fixture_checksum "brew-formulae.txt" 2>/dev/null || true)"
279
+ cask_fixture_checksum="$(catalog_fixture_checksum "brew-casks.txt" 2>/dev/null || true)"
280
+ if [[ -n "${formula_fixture_checksum}" || -n "${cask_fixture_checksum}" ]]; then
281
+ printf "fixture=formula=%s;cask=%s" "${formula_fixture_checksum:-missing}" "${cask_fixture_checksum:-missing}"
282
+ return
283
+ fi
284
+
285
+ if command_exists brew; then
286
+ formula_repo="$(brew --repository 2>/dev/null || true)"
287
+ cask_repo="$(brew --repository homebrew/cask 2>/dev/null || true)"
288
+ fi
289
+
290
+ if command_exists git; then
291
+ if [[ -n "${formula_repo}" && -d "${formula_repo}/.git" ]]; then
292
+ formula_head="$(git -C "${formula_repo}" rev-parse HEAD 2>/dev/null || true)"
293
+ fi
294
+ if [[ -n "${cask_repo}" && -d "${cask_repo}/.git" ]]; then
295
+ cask_head="$(git -C "${cask_repo}" rev-parse HEAD 2>/dev/null || true)"
296
+ fi
297
+ fi
298
+
299
+ if [[ -n "${formula_head}" || -n "${cask_head}" ]]; then
300
+ printf "repos=formula:%s;cask:%s" "${formula_head:-missing}" "${cask_head:-missing}"
301
+ return
302
+ fi
303
+
304
+ if [[ -n "${formula_repo}" || -n "${cask_repo}" ]]; then
305
+ printf "repo-paths=formula:%s;cask:%s" "${formula_repo:-missing}" "${cask_repo:-missing}"
306
+ return
307
+ fi
308
+
309
+ if command_exists brew; then
310
+ brew_version="$(brew --version 2>/dev/null | awk 'NR == 1 { print $0; exit }' || true)"
311
+ if [[ -n "${brew_version}" ]]; then
312
+ printf "version=%s" "${brew_version}"
313
+ return
314
+ fi
315
+ fi
316
+
317
+ printf "state=unknown"
318
+ }
319
+
192
320
  cache_search_catalog_fingerprint() {
193
321
  local manager="$1"
194
322
  local command_path=""
195
- local extra_token=""
323
+ local state_token="state=unknown"
196
324
 
197
325
  case "${manager}" in
198
326
  apt)
199
327
  command_path="$(command -v apt-cache 2>/dev/null || printf "missing")"
200
- if [[ "${FPF_TEST_FIXTURES:-0}" == "1" ]]; then
201
- extra_token="|fixtures=${FPF_TEST_FIXTURE_DIR:-enabled}"
202
- fi
328
+ state_token="$(apt_catalog_state_token)"
203
329
  ;;
204
330
  brew)
205
331
  command_path="$(command -v brew 2>/dev/null || printf "missing")"
206
- if [[ "${FPF_TEST_FIXTURES:-0}" == "1" ]]; then
207
- extra_token="|fixtures=${FPF_TEST_FIXTURE_DIR:-enabled}"
208
- fi
332
+ state_token="$(brew_catalog_state_token)"
209
333
  ;;
210
334
  *)
211
335
  command_path="n/a"
212
336
  ;;
213
337
  esac
214
338
 
215
- printf "%s|cmd=%s%s" "$(cache_fingerprint "${manager}" "" "search-catalog")" "${command_path}" "${extra_token}"
339
+ printf "%s|cmd=%s|%s" "$(cache_fingerprint "${manager}" "" "search-catalog")" "${command_path}" "${state_token}"
216
340
  }
217
341
 
218
342
  cache_search_catalog_key() {
@@ -996,6 +1120,10 @@ manager_can_install_fzf() {
996
1120
  install_fzf_with_manager() {
997
1121
  local manager="$1"
998
1122
 
1123
+ if [[ "${FPF_TEST_FZF_MANAGER_INSTALL_FAIL:-0}" == "1" ]]; then
1124
+ return 1
1125
+ fi
1126
+
999
1127
  case "${manager}" in
1000
1128
  apt)
1001
1129
  run_as_root apt-get install -y fzf
@@ -1034,6 +1162,105 @@ install_fzf_with_manager() {
1034
1162
  esac
1035
1163
  }
1036
1164
 
1165
+ detect_fzf_release_asset() {
1166
+ local os=""
1167
+ local arch=""
1168
+
1169
+ os="$(uname -s)"
1170
+ arch="$(uname -m)"
1171
+
1172
+ case "${os}" in
1173
+ Linux)
1174
+ case "${arch}" in
1175
+ x86_64|amd64) printf "linux_amd64.tar.gz" ;;
1176
+ aarch64|arm64) printf "linux_arm64.tar.gz" ;;
1177
+ armv7l|armv7*) printf "linux_armv7.tar.gz" ;;
1178
+ armv6l|armv6*) printf "linux_armv6.tar.gz" ;;
1179
+ *) return 1 ;;
1180
+ esac
1181
+ ;;
1182
+ Darwin)
1183
+ case "${arch}" in
1184
+ x86_64|amd64) printf "darwin_amd64.tar.gz" ;;
1185
+ arm64|aarch64) printf "darwin_arm64.tar.gz" ;;
1186
+ *) return 1 ;;
1187
+ esac
1188
+ ;;
1189
+ *)
1190
+ return 1
1191
+ ;;
1192
+ esac
1193
+ }
1194
+
1195
+ resolve_fzf_release_url() {
1196
+ local asset_suffix="$1"
1197
+ local api_url="https://api.github.com/repos/junegunn/fzf/releases/latest"
1198
+ local api_payload=""
1199
+ local asset_url=""
1200
+
1201
+ if [[ -n "${FPF_FZF_BOOTSTRAP_URL:-}" ]]; then
1202
+ printf "%s" "${FPF_FZF_BOOTSTRAP_URL}"
1203
+ return 0
1204
+ fi
1205
+
1206
+ command_exists curl || return 1
1207
+ api_payload="$(curl --silent --show-error --fail --location "${api_url}" 2>/dev/null || true)"
1208
+ [[ -n "${api_payload}" ]] || return 1
1209
+
1210
+ asset_url="$(printf "%s" "${api_payload}" | awk -F'"' -v suffix="${asset_suffix}" '
1211
+ $2 == "browser_download_url" {
1212
+ needle = "-" suffix
1213
+ if (index($4, needle) > 0) {
1214
+ print $4
1215
+ exit
1216
+ }
1217
+ }
1218
+ ')"
1219
+
1220
+ [[ -n "${asset_url}" ]] || return 1
1221
+ printf "%s" "${asset_url}"
1222
+ }
1223
+
1224
+ install_fzf_from_release_fallback() {
1225
+ local asset_suffix=""
1226
+ local asset_url=""
1227
+ local archive_file=""
1228
+ local install_dir=""
1229
+
1230
+ if [[ "${FPF_TEST_BOOTSTRAP_FZF_FALLBACK:-0}" == "1" ]]; then
1231
+ if [[ -n "${FPF_TEST_MOCKCMD_PATH:-}" && -n "${FPF_TEST_MOCK_BIN:-}" ]]; then
1232
+ ln -sf "${FPF_TEST_MOCKCMD_PATH}" "${FPF_TEST_MOCK_BIN}/fzf"
1233
+ return 0
1234
+ fi
1235
+ return 1
1236
+ fi
1237
+
1238
+ asset_suffix="$(detect_fzf_release_asset)" || return 1
1239
+ asset_url="$(resolve_fzf_release_url "${asset_suffix}")" || return 1
1240
+ archive_file="$(mktemp "${SESSION_TMP_ROOT}/fzf-bootstrap.XXXXXX.tar.gz")"
1241
+ install_dir="${SESSION_TMP_ROOT}/fzf-bootstrap/bin"
1242
+
1243
+ mkdir -p "${install_dir}"
1244
+
1245
+ if ! curl --silent --show-error --fail --location --output "${archive_file}" "${asset_url}"; then
1246
+ rm -f "${archive_file}"
1247
+ return 1
1248
+ fi
1249
+
1250
+ if ! tar -xzf "${archive_file}" -C "${install_dir}"; then
1251
+ rm -f "${archive_file}"
1252
+ return 1
1253
+ fi
1254
+
1255
+ rm -f "${archive_file}"
1256
+ chmod +x "${install_dir}/fzf" 2>/dev/null || true
1257
+ [[ -x "${install_dir}/fzf" ]] || return 1
1258
+
1259
+ PATH="${install_dir}:${PATH}"
1260
+ export PATH
1261
+ return 0
1262
+ }
1263
+
1037
1264
  build_fzf_bootstrap_candidates() {
1038
1265
  local seen=""
1039
1266
  local manager
@@ -1071,7 +1298,7 @@ ensure_fzf() {
1071
1298
  local candidates=()
1072
1299
  local manager
1073
1300
 
1074
- if [[ "${FPF_TEST_FORCE_FZF_MISSING:-0}" != "1" ]] && command_exists fzf; then
1301
+ if fzf_command_available; then
1075
1302
  return
1076
1303
  fi
1077
1304
 
@@ -1092,12 +1319,17 @@ ensure_fzf() {
1092
1319
  for manager in "${candidates[@]-}"; do
1093
1320
  log "Attempting fzf install with $(manager_label "${manager}")"
1094
1321
  if install_fzf_with_manager "${manager}"; then
1095
- if command_exists fzf; then
1322
+ if fzf_command_available; then
1096
1323
  return
1097
1324
  fi
1098
1325
  fi
1099
1326
  done
1100
1327
 
1328
+ log "Package-manager bootstrap did not provide fzf. Trying release binary fallback."
1329
+ if install_fzf_from_release_fallback && fzf_command_available; then
1330
+ return
1331
+ fi
1332
+
1101
1333
  die "Failed to auto-install fzf. Install fzf manually and rerun."
1102
1334
  }
1103
1335
 
@@ -1314,6 +1546,7 @@ Action options:
1314
1546
  -R, --remove Fuzzy-search installed packages and remove selected
1315
1547
  -U, --update Run manager update/upgrade flow
1316
1548
  --refresh Refresh manager package catalogs only
1549
+ -y, --yes Assume yes for confirmation prompts
1317
1550
  -v, --version Print version and exit
1318
1551
  -h, --help Show this help
1319
1552
 
@@ -1371,6 +1604,8 @@ print_version() {
1371
1604
  }
1372
1605
 
1373
1606
  parse_args() {
1607
+ local manager_value=""
1608
+
1374
1609
  while (($#)); do
1375
1610
  case "$1" in
1376
1611
  -h|--help)
@@ -1391,6 +1626,9 @@ parse_args() {
1391
1626
  --refresh)
1392
1627
  ACTION="refresh"
1393
1628
  ;;
1629
+ -y|--yes)
1630
+ ASSUME_YES="1"
1631
+ ;;
1394
1632
  --feed-search)
1395
1633
  ACTION="feed-search"
1396
1634
  ;;
@@ -1451,7 +1689,16 @@ parse_args() {
1451
1689
  -m|--manager)
1452
1690
  shift
1453
1691
  [[ $# -gt 0 ]] || die "Missing value for --manager"
1454
- MANAGER_OVERRIDE="$(normalize_manager "$1")"
1692
+ manager_value="$(trim_whitespace "$1")"
1693
+ [[ -n "${manager_value}" ]] || die "Missing value for --manager"
1694
+ [[ "${manager_value}" != -* ]] || die "Missing value for --manager"
1695
+ MANAGER_OVERRIDE="$(normalize_manager "${manager_value}")"
1696
+ ;;
1697
+ -m=*|--manager=*)
1698
+ manager_value="$(trim_whitespace "${1#*=}")"
1699
+ [[ -n "${manager_value}" ]] || die "Missing value for --manager"
1700
+ [[ "${manager_value}" != -* ]] || die "Missing value for --manager"
1701
+ MANAGER_OVERRIDE="$(normalize_manager "${manager_value}")"
1455
1702
  ;;
1456
1703
  --)
1457
1704
  shift
@@ -1567,6 +1814,7 @@ rank_display_rows_by_query() {
1567
1814
  }
1568
1815
  }
1569
1816
  {
1817
+ mgr = lower($1)
1570
1818
  pkg = $2
1571
1819
  desc = $3
1572
1820
 
@@ -2619,24 +2867,26 @@ send_fzf_listen_action() {
2619
2867
  payload_size="$(printf "%s" "${action_payload}" | wc -c | tr -d '[:space:]')"
2620
2868
 
2621
2869
  if command_exists curl; then
2622
- curl --silent --show-error --fail --max-time 2 \
2870
+ if curl --silent --show-error --fail --max-time 2 \
2623
2871
  --request POST \
2624
2872
  --header 'Content-Type: text/plain' \
2625
2873
  --data-binary "${action_payload}" \
2626
- "http://${host}:${port}" >/dev/null 2>&1
2627
- return
2874
+ "http://${host}:${port}" >/dev/null 2>&1; then
2875
+ return 0
2876
+ fi
2628
2877
  fi
2629
2878
 
2630
2879
  if command_exists nc; then
2631
- {
2880
+ if {
2632
2881
  printf 'POST / HTTP/1.1\r\n'
2633
2882
  printf 'Host: %s:%s\r\n' "${host}" "${port}"
2634
2883
  printf 'Content-Type: text/plain\r\n'
2635
2884
  printf 'Content-Length: %s\r\n' "${payload_size}"
2636
2885
  printf '\r\n'
2637
2886
  printf '%s' "${action_payload}"
2638
- } | nc -w 2 "${host}" "${port}" >/dev/null 2>&1
2639
- return
2887
+ } | nc -w 2 "${host}" "${port}" >/dev/null 2>&1; then
2888
+ return 0
2889
+ fi
2640
2890
  fi
2641
2891
 
2642
2892
  if exec 9<>"/dev/tcp/${host}/${port}" 2>/dev/null; then
@@ -2728,10 +2978,17 @@ run_ipc_query_notify_action() {
2728
2978
  run_ipc_reload_action "${query}" || send_fzf_prompt_action "Search> " || true
2729
2979
  }
2730
2980
 
2981
+ manager_dispatch_action() {
2982
+ local manager="$1"
2983
+ local action="$2"
2984
+ shift 2 || true
2985
+ manager_execute_action "${manager}" "${action}" "$@"
2986
+ }
2987
+
2731
2988
  manager_install() {
2732
2989
  local manager="$1"
2733
2990
  shift
2734
- manager_execute_action "${manager}" "install" "$@"
2991
+ manager_dispatch_action "${manager}" "install" "$@"
2735
2992
  }
2736
2993
 
2737
2994
  manager_remove() {
@@ -2759,12 +3016,19 @@ manager_refresh() {
2759
3016
  confirm_action() {
2760
3017
  local prompt="$1"
2761
3018
  local reply=""
3019
+ local normalized_reply=""
3020
+
3021
+ if assume_yes_enabled; then
3022
+ return 0
3023
+ fi
2762
3024
 
2763
3025
  printf "%s [y/N]: " "${prompt}" >&2
2764
3026
  read -r reply || true
3027
+ reply="$(trim_whitespace "${reply}")"
3028
+ normalized_reply="$(printf "%s" "${reply}" | tr '[:upper:]' '[:lower:]')"
2765
3029
 
2766
- case "${reply}" in
2767
- y|Y|yes|YES)
3030
+ case "${normalized_reply}" in
3031
+ y|yes)
2768
3032
  return 0
2769
3033
  ;;
2770
3034
  *)
package/fpf.png ADDED
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fpf-cli",
3
- "version": "1.6.30",
3
+ "version": "1.6.32",
4
4
  "description": "Cross-platform fuzzy package finder powered by fzf",
5
5
  "bin": {
6
6
  "fpf": "fpf"
@@ -9,7 +9,8 @@
9
9
  "fpf",
10
10
  "lib/fpf/*.sh",
11
11
  "README.md",
12
- "LICENSE"
12
+ "LICENSE",
13
+ "fpf.png"
13
14
  ],
14
15
  "keywords": [
15
16
  "fzf",