Recently Written · git

sbm

dmenu bookmarks. Instantly fuzzy search thousands of bookmarks and plumb them. LOOKING FOR SUCKLESS SOFTWARE EDITION? GO TO sbm-suckless INSTEAD

git clone https://github.com/equwal/sbm

Log | Files | Refs


README (13611 bytes)

1 Manage bookmarks with dmenu and a basic file.
2 
3 The file is Tab Separated Values, one bookmark per line:
4 
5     URL<tab>description<tab>tag tag tag
6 
7 For example:
8 
9     https://equwal.com	Spenser Truex's website.	users
10 
11 Lines starting with # are ignored. bm is one way to work with the file;
12 cut, grep, awk and your editor are others.
13 
14 == Design ==
15 bm owns the bookmark file and talks to the menu. Everything else is a small
16 filter that reads or writes bookmark lines, so the pieces combine like any
17 other Unix tools, and you install only the ones you use:
18 
19     bm          pick, open, copy, add, edit, delete, list, merge
20     bm-import   browser bookmarks        -> bookmark lines
21     bm-check    bookmark lines           -> the dead links among them
22     bm-html     bookmark lines           -> a searchable web page
23     bm-migrate  the old sbm file format  -> bookmark lines
24     bm-title    a URL                    -> the page's title
25     bm-commit   commit the bookmark file to git
26     bm-watch    browser bookmarks        -> bm, now and after each change
27     bm-sync     the bookmark file        <-> your other devices
28 
29     bm-import bookmarks.html | bm --merge
30     bm --tag code --list | bm-check -
31     bm --list | grep -i wiki | bm-html -t Wikis - > wikis.html
32 
33 Everything is POSIX sh and POSIX utilities: no bash, no mktemp, no GNU
34 options. make check runs shellcheck -s sh over all of it. Where the usual
35 tool is not in POSIX, the script says what stands in for it: noclobber for
36 mktemp, a FIFO of tokens for xargs -P, dd for head -c, awk's srand() for
37 date +%s.
38 
39 == Requirements ==
40 dmenu (or fzf), one of xclip, xsel or wl-clipboard, sh, awk, make.
41 curl for bm-title, bm-check and bm-sync, jq for bm-import of Chromium files,
42 git for bm-commit.
43 
44 == Install ==
45 Edit TOOLS in config.mk to choose what gets installed, then
46 
47     make install                        # to /usr/local/bin
48     make PREFIX="$HOME/.local" install
49 
50 Each release on GitHub also has an installer for Windows and a portable app
51 for macOS. See Windows and macOS below.
52 
53 == Setup ==
54 Nothing is required. Bookmarks are kept in ~/.local/share/sbm/bookmarks
55 ($XDG_DATA_HOME is honoured) and the file is created on first use.
56 
57 Tag choices are read from ~/.local/share/sbm/usertags:
58 
59     <tag><space>|<space><description><newline>
60 
61 Start from the included example, or from nothing: a tag typed into the tag
62 menu that is not in the file yet is added to it.
63 
64     mkdir -p ~/.local/share/sbm
65     cp usertags engines ~/.local/share/sbm/
66 
67 == Usage ==
68     bm                   browse (see below)
69     bm -o, --open        open a bookmark, an address, or a web search
70                          (-p, --plumb is the same)
71     bm -c, --copy        copy a bookmark's URL to the clipboard
72     bm --print           print a bookmark's URL
73     bm -a, --add [url]   add a bookmark
74     bm -e, --edit        open the file in $VISUAL/$EDITOR at a bookmark
75     bm -d, --delete      delete a bookmark
76     bm -l, --list        print the bookmarks
77     bm -m, --merge       add the bookmark lines on stdin, skipping known ones
78     -t, --tag TAG        restrict any of the above to one tag
79     -S, --sort ORDER     used (default), recent, url, desc or tag
80 
81 Bind "bm" and "bm -a" to keys in your window manager.
82 
83 == Browsing ==
84 Plain "bm" lists the bookmarks. Pick one and a second menu shows it in full
85 (dmenu cannot preview while you scroll, so this is its preview) and asks what
86 to do:
87 
88     open
89     copy
90     edit
91     delete
92       https://suckless.org/
93       software that sucks less
94       tags: code org
95       used: 12
96 
97 Closing the second menu goes back to the list, closing the list leaves.
98 [add] at the top of the list adds a bookmark.
99 
100 With fzf (no display, or SBM_MENU=fzf) it is one screen: the bookmark under
101 the cursor is previewed live, and keys do the rest.
102 
103     ENTER   open            ctrl-a  add            ctrl-s  next sort order
104     ctrl-y  copy            ctrl-d  delete (asks)
105     ESC     leave           ctrl-e  edit
106 
107 To see the bookmarks of a tag, type the tag. fzf searches the tags too.
108 
109 == Web search ==
110 In the list, and in "bm -o", text that is not a bookmark is not wasted:
111 
112     suckless.org/dwm          one word with a dot or "://": opened as an address
113     posix sh printf           anything else: searched for on the web
114     w dynamic menu            first word is a keyword: that engine is used
115 
116 So one key gives you bookmarks first and the web behind them. The default
117 engine is DuckDuckGo; set another with SBM_SEARCH='https://.../?q=%s'.
118 Keywords are read from ~/.local/share/sbm/engines:
119 
120     <keyword><space>|<space><url with %s><newline>
121 
122 See the included engines file.
123 
124 == Adding ==
125 Without a url, bm offers the X11 primary selection, or the clipboard, when
126 it holds a URL. With bm-title installed the description defaults to the
127 page's title (curl, 3 second limit; SBM_FETCH=0 turns it off). In dmenu the
128 default is the one entry in the menu: Return accepts it, Shift-Return sends
129 exactly what you typed.
130 
131 Tags are chosen one at a time until "done" is picked or the menu is closed
132 (fzf: TAB marks, ENTER confirms). Typing a tag that does not exist creates it.
133 
134 A URL that is already bookmarked is refused (exit status 3). URLs are
135 compared without their scheme, a leading www. and trailing slashes.
136 
137 == Sorting ==
138     used     most opened or copied first, then by url (the default)
139     recent   most recently added first
140     url, desc, tag
141 
142 Set a default with SBM_SORT. Use counts live in "$BOOKMARKS.usage", so the
143 bookmark file itself stays a plain list.
144 
145 == A web page of your bookmarks ==
146     bm-html > ~/public_html/bookmarks.html
147 
148 One self-contained file: no server side, nothing loaded from elsewhere.
149 Bookmarks are listed under each of their tags, with the tags linked at the
150 top. A dozen lines of inline script add a filter box; without script the page
151 is still the complete list, and Ctrl-F works. page.html?q=words starts out
152 filtered, so the page can be a search keyword in your browser, or in bm:
153 
154     b | https://example.org/bookmarks.html?q=%s
155 
156 Links that would run script (javascript:, data:) are never written.
157 
158 == Importing ==
159     bm-import bookmarks.html | bm --merge     # the HTML export of any browser
160     bm-import vivaldi | bm --merge            # or chrome, brave, edge, chromium
161     bm-import path/to/Bookmarks | bm --merge  # a Chromium JSON file (needs jq)
162 
163 A browser name imports the bookmarks of all profiles of that browser. The
164 profiles are in ~/.config, in ~/Library/Application Support on macOS, or in
165 %LOCALAPPDATA% on Windows. Folder names become tags ("Dev Tools" becomes
166 dev-tools). bm --merge skips what you already have, so importing twice adds
167 nothing. To sync new browser bookmarks into bm, run the same command again.
168 Look before you merge: bm-import prints plain bookmark lines.
169 
170 bm-watch does this import at once, and again each time that the bookmark
171 file of a profile changes. It only adds bookmarks, and it never writes to
172 the browser:
173 
174     bm-watch brave &
175 
176 == Dead links ==
177     bm-check
178 
179 prints status<tab>url for links that fail, and status<tab>url<tab>new-url for
180 ones that moved for good. It exits 1 if it printed anything.
181 
182 == History and sync with git ==
183 With bm-commit installed, every change bm makes is committed, provided the
184 directory holding the bookmark file is a git repository of its own. It never
185 pushes or pulls.
186 
187     cd ~/.local/share/sbm && git init
188     printf '%s\n' '*.usage' '*.bak*' > .gitignore
189 
190 A file that merely sits inside a bigger repository (your dotfiles) is left
191 alone unless SBM_GIT=1. SBM_GIT=0 turns commits off.
192 
193 == Sync ==
194 bm-sync keeps the bookmark file the same on all your computers, the sbm app
195 for Android and the sbm add-on for Firefox and Chrome.
196 
197     bm-sync login        # once on each computer: email and password
198     bm-sync              # sync now
199 
200 When bm-sync is installed and signed in, bm syncs after each change, in the
201 background. Bookmarks that you add or remove on one device are added or
202 removed on the others. The file stays a plain file, and bm works without a
203 network. The sign-in token is kept in ~/.config/sbm/sync.
204 
205 An empty or missing bookmark file removes nothing on the other devices:
206 bm-sync gets all the bookmarks back from the server.
207 
208 bm and bm-sync lock the file for each change, with the directory
209 $BOOKMARKS.lock, so that neither writes over a change of the other. bm -e
210 holds the lock until the editor ends; bm-sync waits for it, and bm syncs
211 the edit after. A lock left by a process that is gone is taken over.
212 
213 The server is sbm-sync (https://github.com/equwal/sbm-sync). bm-sync uses
214 https://sbm.subread.space unless you give another server: create an account
215 there. The server is free software, so you can run your own:
216 
217     bm-sync login https://sbm.example.org
218 
219 The sbm app for Android is at https://github.com/equwal/sbm-android. A demo of
220 sync between bm and the browser add-on (80 seconds):
221 https://github.com/equwal/sbm-sync#sbm-sync
222 
223 == Migrating from the old format ==
224 Earlier versions wrote "URL description | tag tag".
225 
226     bm-migrate                  # $BOOKMARKS, or the default file
227     bm-migrate -n file          # only show what it would become
228     bm-migrate - < old > new    # as a filter
229 
230 The original is kept as .bak, and an existing .bak is never overwritten.
231 
232 == Configuration ==
233 All through the environment, all optional.
234 
235     BOOKMARKS     bookmark file
236     USERTAGS      tag choices file
237     DMENULINES    lines shown by dmenu (default 15)
238     SBM_MENU      dmenu, fzf, or a custom command
239     SBM_FZF_OPTS  extra options for every fzf call
240     SBM_SORT      default sort order
241     SBM_SEARCH    default search engine, with %s for the query
242     SBM_ENGINES   search keywords file
243     SBM_COPY      command that reads the new clipboard contents from stdin
244     SBM_PASTE     command that writes the clipboard contents to stdout
245     SBM_OPEN      command that opens the URL given as its argument
246     SBM_LOCK_WAIT seconds that bm and bm-sync wait for the lock (default 10)
247 
248 With a display bm uses dmenu; fzf is only used when dmenu is missing or
249 there is no X11 or Wayland display. A custom SBM_MENU is called with the
250 choices on stdin as one of:
251 
252     <command> pick  <prompt>     print the chosen line, or the typed text
253     <command> multi <prompt>     print every chosen line
254     <command> ask   <prompt>     print free text; stdin holds a default, if any
255 
256 Printing nothing means the user cancelled. test/fakemenu is a small example.
257 
258 == Windows ==
259 bm runs under Cygwin. Get sbm-VERSION-setup.exe from a release on GitHub and
260 start it. The installer puts bm and the bm-* tools in /usr/local/bin. It
261 makes a Start menu entry and a desktop shortcut that open bm in a mintty
262 window, and Esc closes the window. The key of the desktop shortcut is
263 Ctrl+Alt+Shift+F24. Almost no keyboard has an F24 key, so keyboard firmware
264 can use this key combination without conflicts.
265 
266 The installer imports the bookmarks of all profiles of the Chromium browsers
267 (Brave, Chrome, Chromium, Edge and Vivaldi). A Startup entry, sbm-watch, runs
268 bm-watch at each login, so new browser bookmarks come into bm a few seconds
269 after you add them. The import needs the jq package of Cygwin.
270 
271 The installer also puts fzf.exe, the Windows build of fzf, in
272 /usr/local/libexec/sbm. Do not use the fzf package of Cygwin. That package is
273 version 0.11 from 2016, and it has no --preview and no --expect. The wrapper
274 /usr/local/bin/fzf lets fzf.exe run in mintty. /etc/profile.d/sbm.sh sets
275 SBM_OPEN, SBM_COPY, SBM_PASTE and SBM_FZF_OPTS for Cygwin. Settings in
276 ~/.bash_profile win.
277 
278 For bm-title, bm-check and bm-import, install the curl and jq packages of
279 Cygwin. The Windows builds of curl and jq do not understand Cygwin paths,
280 such as /dev/null. To remove sbm, use Apps in the Windows settings. The
281 uninstaller keeps your bookmarks. contrib/windows/build-installer.sh makes
282 the installer with NSIS.
283 
284 == macOS ==
285 Get sbm-VERSION.dmg from a release on GitHub, and drag sbm.app to
286 Applications. The app contains bm, the bm-* tools and fzf, so it needs no
287 other software. sbm.app opens bm in a Terminal window. The first time, macOS
288 asks if sbm can control Terminal.
289 
290 The app has no signature from a registered developer, so macOS stops it the
291 first time. To open it, use Open Anyway in System Settings, Privacy &
292 Security. Or remove the quarantine attribute:
293 
294     xattr -dr com.apple.quarantine /Applications/sbm.app
295 
296 To open sbm with a key, make a shortcut with the action Open App in the
297 Shortcuts app, and give it a keyboard shortcut. macOS has function keys up
298 to F20. To use the tools in a shell, add these lines to ~/.zprofile:
299 
300     PATH=/Applications/sbm.app/Contents/Resources/bin:$PATH
301     export SBM_OPEN=open SBM_COPY=pbcopy SBM_PASTE=pbpaste
302 
303 contrib/macos/build-dmg.sh makes the image on a Mac.
304 
305 == Tests ==
306     make check
307 
308 runs shellcheck, when installed, and test/run.sh, under sh. Run the tests
309 under another shell with SBM_SH=dash sh test/run.sh. They script dmenu, fzf
310 and curl, so they need no display and no network.
311 
312 == Releases ==
313 Set VERSION in config.mk. Tag the release as vVERSION. make dist makes the
314 tarball. contrib/windows/build-installer.sh makes the Windows installer. On
315 a Mac, contrib/macos/build-dmg.sh makes the image. Attach the three files to
316 the GitHub release of the tag.
317 
318 == Credits ==
319 Some ideas taken from Karl Voit:
320 https://karl-voit.at/2022/01/29/How-to-Use-Tags/
321 
322 == License ==
323 AGPL-3.0: see LICENSE. If you run a changed version for other people over a
324 network, give them its source code.
325 
326 If sbm is useful to you, you can support it on Ko-fi:
327 https://ko-fi.com/truex