fpf-cli 1.6.30 → 1.6.31

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 +268 -18
  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.31"
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
 
@@ -1391,6 +1624,9 @@ parse_args() {
1391
1624
  --refresh)
1392
1625
  ACTION="refresh"
1393
1626
  ;;
1627
+ -y|--yes)
1628
+ ASSUME_YES="1"
1629
+ ;;
1394
1630
  --feed-search)
1395
1631
  ACTION="feed-search"
1396
1632
  ;;
@@ -1567,6 +1803,7 @@ rank_display_rows_by_query() {
1567
1803
  }
1568
1804
  }
1569
1805
  {
1806
+ mgr = lower($1)
1570
1807
  pkg = $2
1571
1808
  desc = $3
1572
1809
 
@@ -2619,24 +2856,26 @@ send_fzf_listen_action() {
2619
2856
  payload_size="$(printf "%s" "${action_payload}" | wc -c | tr -d '[:space:]')"
2620
2857
 
2621
2858
  if command_exists curl; then
2622
- curl --silent --show-error --fail --max-time 2 \
2859
+ if curl --silent --show-error --fail --max-time 2 \
2623
2860
  --request POST \
2624
2861
  --header 'Content-Type: text/plain' \
2625
2862
  --data-binary "${action_payload}" \
2626
- "http://${host}:${port}" >/dev/null 2>&1
2627
- return
2863
+ "http://${host}:${port}" >/dev/null 2>&1; then
2864
+ return 0
2865
+ fi
2628
2866
  fi
2629
2867
 
2630
2868
  if command_exists nc; then
2631
- {
2869
+ if {
2632
2870
  printf 'POST / HTTP/1.1\r\n'
2633
2871
  printf 'Host: %s:%s\r\n' "${host}" "${port}"
2634
2872
  printf 'Content-Type: text/plain\r\n'
2635
2873
  printf 'Content-Length: %s\r\n' "${payload_size}"
2636
2874
  printf '\r\n'
2637
2875
  printf '%s' "${action_payload}"
2638
- } | nc -w 2 "${host}" "${port}" >/dev/null 2>&1
2639
- return
2876
+ } | nc -w 2 "${host}" "${port}" >/dev/null 2>&1; then
2877
+ return 0
2878
+ fi
2640
2879
  fi
2641
2880
 
2642
2881
  if exec 9<>"/dev/tcp/${host}/${port}" 2>/dev/null; then
@@ -2728,10 +2967,17 @@ run_ipc_query_notify_action() {
2728
2967
  run_ipc_reload_action "${query}" || send_fzf_prompt_action "Search> " || true
2729
2968
  }
2730
2969
 
2970
+ manager_dispatch_action() {
2971
+ local manager="$1"
2972
+ local action="$2"
2973
+ shift 2 || true
2974
+ manager_execute_action "${manager}" "${action}" "$@"
2975
+ }
2976
+
2731
2977
  manager_install() {
2732
2978
  local manager="$1"
2733
2979
  shift
2734
- manager_execute_action "${manager}" "install" "$@"
2980
+ manager_dispatch_action "${manager}" "install" "$@"
2735
2981
  }
2736
2982
 
2737
2983
  manager_remove() {
@@ -2760,6 +3006,10 @@ confirm_action() {
2760
3006
  local prompt="$1"
2761
3007
  local reply=""
2762
3008
 
3009
+ if assume_yes_enabled; then
3010
+ return 0
3011
+ fi
3012
+
2763
3013
  printf "%s [y/N]: " "${prompt}" >&2
2764
3014
  read -r reply || true
2765
3015
 
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.31",
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",