Skip to content

Instantly share code, notes, and snippets.

@eevmanu
Created August 26, 2026 02:37
Show Gist options
  • Select an option

  • Save eevmanu/24fabb26e5cbf1d14a92810da3e06bbd to your computer and use it in GitHub Desktop.

Select an option

Save eevmanu/24fabb26e5cbf1d14a92810da3e06bbd to your computer and use it in GitHub Desktop.
Interactive CLI Chrome Bookmarks fuzzy search and clipboard copy (jq + fzf + wl-copy)
#!/usr/bin/env bash
# ==============================================================================
# TOOL: Chrome Bookmarks Interactive Fuzzy Search & Clipboard Tool
# ==============================================================================
#
# USAGE:
# Run this command directly in your terminal, or bind it to a bash/zsh function.
#
# ------------------------------------------------------------------------------
# PIPELINE ARCHITECTURE BREAKDOWN
# ------------------------------------------------------------------------------
# 1. JQ (JSON Extraction & Table Rendering):
# - Chrome's 'Bookmarks' file is plain JSON (NOT an SQLite database; commands
# like 'sqlite3 .tables' will not work on it).
# - Traversal: Uses the recursive descent operator '..' across all folders
# inside '.roots' ('bookmark_bar', 'other', 'synced', and any nested sub-folders).
# - Filter: 'select(.type == "url")' isolates leaf bookmarks from folders.
# - Sorting: 'sort_by(.name // "" | ascii_downcase)' sorts A-Z case-insensitively.
# - Sanitization: 'gsub("[\t\r\n]+"; " ")' replaces tabs/newlines in bookmark
# titles with spaces to prevent breaking the Tab-Separated Values (TSV) stream.
# - Fixed-Width Padding ('pad(45)'):
# * Padded with spaces to 45 chars, or truncated with '…' if longer.
# * Ensures the vertical divider '│' lines up in a straight table column.
# - 3-Field TSV Output (Decoupling Search from Display):
# * Field 1 (Hidden): Raw URL (for clean extraction on selection).
# * Field 2 (Hidden): Raw full title (for 100% lossless search).
# * Field 3 (Visible): ANSI-colored formatted table row:
# [Bold Cyan Title (45c)] [Gray │ Divider] [Green URL]
#
# 2. FZF (Fuzzy Interactive Search):
# - '--ansi': Interprets ANSI color escape codes cleanly.
# - '--delimiter=\t': Splits fields by tabs.
# - '--with-nth=3': Renders ONLY Field 3 (the clean aligned table) in the UI.
# - '--nth=1,2': Targets BOTH raw URL (Field 1) and full Title (Field 2) for
# full-text matching. Even if a title was truncated to 45 chars visually,
# fzf still searches against 100% of the hidden full title and URL.
# - '--layout=reverse': Places search input prompt at the TOP with top-to-bottom results.
#
# 3. CUT (Result Extraction):
# - 'cut -f1': Extracts only Field 1 (the clean, raw URL) upon pressing <Enter>.
#
# 4. TEE & WL-COPY (Dual Output & Wayland Clipboard):
# - 'tee /dev/stderr': Prints the chosen URL to the terminal screen.
# - 'wl-copy': Native Wayland clipboard utility; copies the URL to clipboard.
#
# ------------------------------------------------------------------------------
# CHAIN OF THOUGHT & HISTORICAL DESIGN DECISIONS
# ------------------------------------------------------------------------------
# - Why not SQLite?
# Unlike History/Cookies/Web Data, Chrome Bookmarks is purely JSON.
# - Why recursive '..'?
# Bookmarks can be nested arbitrarily deep inside folder hierarchies.
# - Why not a simple 'name\turl' display?
# Without color coding and fixed-width padding, long titles caused the title
# and URL to merge into one continuous line, making visual scanning difficult.
# - Why 3 decoupled TSV fields instead of 1 or 2?
# Directly truncating text would break searchability for words past char 45.
# Keeping raw title/URL in Fields 1 & 2 while displaying Field 3 gives the best
# of both worlds: a clean table layout + 100% lossless search.
# - Why 'wl-copy' over 'xclip'/'xsel'?
# On Wayland sessions, 'wl-copy' is native and persistent, avoiding XWayland
# emulation drops when the subshell closes.
#
# ------------------------------------------------------------------------------
# KNOWN CAVEATS & TRADE-OFFS
# ------------------------------------------------------------------------------
# - Cancellation on <Esc>:
# Piping directly to 'wl-copy' without checking for empty strings will clear
# the clipboard if you cancel fzf with <Esc> or <Ctrl+C>.
# (To make it cancel-safe, switch to: '... | xargs -r -I{} sh -c "wl-copy -n \"{}\"; echo \"{}\""')
# - Stderr Output:
# 'tee /dev/stderr' writes to stderr. It displays on your screen, but cannot
# be piped downstream (e.g., 'cb | xdg-open' will receive nothing on stdin).
# - Trailing Newline:
# Standard 'wl-copy' copies a trailing newline. Use 'wl-copy -n' if you want
# to paste into address bars without auto-submitting.
#
# ------------------------------------------------------------------------------
# FUTURE ITERATION IDEAS
# ------------------------------------------------------------------------------
# - Dynamic Profile Detection: Replace 'Profile 3' with a parameter or fzf selector.
# - Bottom Preview Window: Add '--preview' and '--preview-window=down:3:wrap' to
# read full titles of truncated rows on hover.
# - Direct Browser Launcher: Append '&& google-chrome "$url"' to open on Enter.
# ==============================================================================
jq -r '
def sanitize: gsub("[\t\r\n]+"; " ");
def pad(n): if (length > n) then (.[:n-1] + "…") else (. + (" " * (n - length))) end;
[.roots | .. | objects | select(.type == "url")]
| sort_by(.name // "" | ascii_downcase)
| .[]
| ((.name // "Untitled") | sanitize) as $name
| "\(.url)\t\($name)\t\u001b[1;36m\($name | pad(45))\u001b[0m \u001b[38;5;240m│\u001b[0m \u001b[32m\(.url)\u001b[0m"
' "/home/user/.config/google-chrome/Profile X/Bookmarks" \
| fzf --ansi --delimiter='\t' --with-nth=3 --nth=1,2 --layout=reverse \
| cut -f1 \
| tee /dev/stderr \
| wl-copy
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment