A .local/bin/bm => .local/bin/bm +100 -0
@@ 0,0 1,100 @@
+#!/usr/bin/env bash
+###############################################################################
+# bm — Bookmark manager CLI
+#
+# Usage:
+# bm add <url> [name] [category] [tags] — add a bookmark
+# bm rm <url|name> — remove a bookmark
+# bm ls [category] — list bookmarks
+# bm cats — list categories
+# bm search <query> — search by name/url/tags
+# bm edit <url|name> — edit a bookmark in $EDITOR
+###############################################################################
+
+DB="${XDG_DATA_HOME:-$HOME/.local/share}/karbs/bookmarks.db"
+
+sq() { printf '%s' "${1//\'/\'\'}"; }
+
+cmd="${1:-ls}"
+shift
+
+case "$cmd" in
+ add)
+ url="${1:?usage: bm add <url> [name] [category] [tags]}"
+ name="${2:-$(printf '%s' "$url" | sed 's|https\?://||; s|^www\.||; s|/$||; s|/.*||')}"
+ category="${3:-Uncategorized}"
+ tags="${4:-}"
+ safe_url=$(sq "$url"); safe_name=$(sq "$name")
+ safe_cat=$(sq "$category"); safe_tags=$(sq "$tags")
+ sqlite3 "$DB" "INSERT OR REPLACE INTO bookmarks (name,url,category,tags)
+ VALUES ('${safe_name}','${safe_url}','${safe_cat}','${safe_tags}');"
+ echo "Added: [${category}] ${name}"
+ ;;
+
+ rm)
+ term="${1:?usage: bm rm <url|name>}"
+ safe=$(sq "$term")
+ sqlite3 "$DB" "DELETE FROM bookmarks WHERE url='${safe}' OR name='${safe}';"
+ echo "Removed: ${term}"
+ ;;
+
+ ls)
+ filter="${1:-}"
+ if [ -n "$filter" ]; then
+ safe=$(sq "$filter")
+ sqlite3 "$DB" -column -header \
+ "SELECT id,category,name,url,tags FROM bookmarks WHERE category='${safe}' ORDER BY name;"
+ else
+ sqlite3 "$DB" -column -header \
+ "SELECT id,category,name,url,tags FROM bookmarks ORDER BY category,name;"
+ fi
+ ;;
+
+ cats)
+ sqlite3 "$DB" "SELECT category, COUNT(*) as count FROM bookmarks GROUP BY category ORDER BY category;"
+ ;;
+
+ search)
+ query="${1:?usage: bm search <query>}"
+ safe=$(sq "$query")
+ sqlite3 "$DB" -column -header "
+ SELECT category, name, url, tags FROM bookmarks
+ WHERE name LIKE '%${safe}%'
+ OR url LIKE '%${safe}%'
+ OR tags LIKE '%${safe}%'
+ OR category LIKE '%${safe}%'
+ ORDER BY category, name;"
+ ;;
+
+ edit)
+ term="${1:?usage: bm edit <url|name>}"
+ safe=$(sq "$term")
+ row=$(sqlite3 "$DB" -separator $'\t' \
+ "SELECT id,name,url,category,tags,description FROM bookmarks WHERE url='${safe}' OR name='${safe}' LIMIT 1;")
+ [ -z "$row" ] && echo "Not found: $term" && exit 1
+ id=$(cut -f1 <<< "$row")
+ tmp=$(mktemp /tmp/bm-edit.XXXXXX)
+ printf '%s' "$row" | awk -F'\t' '{
+ print "name: " $2
+ print "url: " $3
+ print "category: " $4
+ print "tags: " $5
+ print "description: " $6
+ }' > "$tmp"
+ ${EDITOR:-nvim} "$tmp"
+ name=$(grep '^name:' "$tmp" | sed 's/^name: *//')
+ url=$(grep '^url:' "$tmp" | sed 's/^url: *//')
+ cat=$(grep '^category:' "$tmp" | sed 's/^category: *//')
+ tags=$(grep '^tags:' "$tmp" | sed 's/^tags: *//')
+ desc=$(grep '^description:' "$tmp" | sed 's/^description: *//')
+ rm -f "$tmp"
+ sn=$(sq "$name"); su=$(sq "$url"); sc=$(sq "$cat"); st=$(sq "$tags"); sd=$(sq "$desc")
+ sqlite3 "$DB" "UPDATE bookmarks SET name='${sn}',url='${su}',category='${sc}',tags='${st}',description='${sd}' WHERE id=${id};"
+ echo "Updated: [${cat}] ${name}"
+ ;;
+
+ *)
+ echo "Usage: bm <add|rm|ls|cats|search|edit> [args]"
+ exit 1
+ ;;
+esac
A .local/bin/dsnip => .local/bin/dsnip +68 -0
@@ 0,0 1,68 @@
+#!/usr/bin/env bash
+###############################################################################
+# dsnip — Bookmark launcher: category browse + full-text search via dmenu
+#
+# DB: ~/.local/share/karbs/bookmarks.db
+# Usage:
+# dsnip — open launcher
+# dsnip-seed — import legacy snippets file (run once)
+# bm add — add a bookmark from the terminal
+#
+# Maintainer: Kris Yotam <krisyotam@pm.me>
+# License: MIT
+# Created: 2026-04-05
+###############################################################################
+
+DB="${XDG_DATA_HOME:-$HOME/.local/share}/karbs/bookmarks.db"
+CAT_ICON=" "
+DM="dmenu -i -l 25 -c -bw 2"
+
+sqlite3 "$DB" "
+ CREATE TABLE IF NOT EXISTS bookmarks (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ name TEXT NOT NULL,
+ url TEXT NOT NULL UNIQUE,
+ category TEXT NOT NULL DEFAULT 'Uncategorized',
+ tags TEXT NOT NULL DEFAULT '',
+ description TEXT NOT NULL DEFAULT ''
+ );
+" 2>/dev/null
+
+# Escape single quotes for inline SQL
+sq() { printf '%s' "${1//\'/\'\'}"; }
+
+build_list() {
+ sqlite3 "$DB" "SELECT DISTINCT '${CAT_ICON}' || category FROM bookmarks ORDER BY category;"
+ printf '%.0s─' {1..40}; echo
+ sqlite3 "$DB" "
+ SELECT '[' || category || '] ' || name ||
+ CASE WHEN tags != '' THEN ' # ' || tags ELSE '' END
+ FROM bookmarks ORDER BY category, name;"
+}
+
+chosen=$(build_list | $DM -p " Bookmarks")
+[ -z "$chosen" ] && exit 0
+[[ "$chosen" == ─* ]] && exit 0
+
+if [[ "$chosen" == "${CAT_ICON}"* ]]; then
+ cat="${chosen#${CAT_ICON}}"
+ safe_cat=$(sq "$cat")
+ chosen2=$(sqlite3 "$DB" "
+ SELECT name || CASE WHEN tags != '' THEN ' # ' || tags ELSE '' END
+ FROM bookmarks WHERE category = '${safe_cat}' ORDER BY name;" \
+ | $DM -p " ${cat}")
+ [ -z "$chosen2" ] && exit 0
+ name=$(printf '%s' "$chosen2" | sed 's/ # .*//')
+ safe_name=$(sq "$name")
+ url=$(sqlite3 "$DB" "SELECT url FROM bookmarks WHERE category = '${safe_cat}' AND name = '${safe_name}' LIMIT 1;")
+else
+ name_part=$(printf '%s' "$chosen" | sed 's/ # .*//')
+ cat=$(printf '%s' "$name_part" | sed 's/^\[\([^]]*\)\].*/\1/')
+ name=$(printf '%s' "$name_part" | sed 's/^\[[^]]*\] //')
+ safe_cat=$(sq "$cat")
+ safe_name=$(sq "$name")
+ url=$(sqlite3 "$DB" "SELECT url FROM bookmarks WHERE category = '${safe_cat}' AND name = '${safe_name}' LIMIT 1;")
+fi
+
+[ -z "$url" ] && exit 0
+xdg-open "$url" &>/dev/null & disown
A .local/bin/dsnip-seed => .local/bin/dsnip-seed +59 -0
@@ 0,0 1,59 @@
+#!/usr/bin/env bash
+###############################################################################
+# dsnip-seed — Import legacy karbs/snippets file into bookmarks.db
+#
+# Run once after creating the db. Safe to re-run (UNIQUE on url skips dupes).
+###############################################################################
+
+DB="${XDG_DATA_HOME:-$HOME/.local/share}/karbs/bookmarks.db"
+SNIPPETS="${XDG_DATA_HOME:-$HOME/.local/share}/karbs/snippets"
+
+[ -f "$SNIPPETS" ] || { echo "snippets file not found: $SNIPPETS"; exit 1; }
+
+category="Uncategorized"
+inserted=0
+skipped=0
+
+while IFS= read -r line; do
+ # Strip trailing whitespace
+ line=$(printf '%s' "$line" | sed 's/[[:space:]]*$//')
+
+ # Skip blank lines
+ [ -z "$line" ] && continue
+
+ # Category header (must check before stripping inline comments)
+ if [[ "$line" == \#* ]] && [[ "$line" != http* ]]; then
+ category=$(printf '%s' "$line" | sed 's/^# *//')
+ continue
+ fi
+
+ # Strip inline comments (e.g. "https://... # Profile")
+ line=$(printf '%s' "$line" | sed 's/ *#[^/].*//')
+
+ # Skip non-URL lines
+ [[ "$line" != http* ]] && continue
+
+ url=$(printf '%s' "$line" | sed 's/[[:space:]]*$//')
+ # Derive a display name: strip scheme + www, truncate at 60 chars
+ name=$(printf '%s' "$url" | sed 's|https\?://||; s|^www\.||; s|/$||')
+ name="${name:0:60}"
+
+ # Escape single quotes
+ safe_url="${url//\'/\'\'}"
+ safe_name="${name//\'/\'\'}"
+ safe_cat="${category//\'/\'\'}"
+
+ result=$(sqlite3 "$DB" "
+ INSERT OR IGNORE INTO bookmarks (name, url, category)
+ VALUES ('${safe_name}', '${safe_url}', '${safe_cat}');
+ SELECT changes();
+ ")
+
+ if [ "$result" -eq 1 ]; then
+ ((inserted++))
+ else
+ ((skipped++))
+ fi
+done < "$SNIPPETS"
+
+echo "Done. Inserted: $inserted Skipped (already exist): $skipped"