mark

Privacy-first CLI bookmark manager — SQLite + FTS5 full-text search

v1.0.1
NixOS x86_64

Quick Start

Install via jcli (recommended)

jcli install mark

Or via curl + bash

curl -fsSL https://cli.johlem.net/tools/mark/install.sh | bash

First-run smoke test

mark init
mark add https://doc.rust-lang.org/book/ -t "The Rust Book" -T rust,docs --rate 5 --pin
mark add https://attack.mitre.org -t "MITRE ATT&CK" -T security -C pentest
mark ls
mark search "rust"
mark stats
mark export --format json --pretty

What it does

mark is a privacy-first bookmark manager that lives entirely in your terminal. Bookmarks sit in a single SQLite file under ~/.local/share/mark/, indexed by FTS5 for instant full-text search across URL, title, description, and notes. No browser extension, no daemon, no cloud account — just one binary that pipes cleanly into fzf, rofi, xargs, and the rest of your shell.

Subcommands

CommandWhat it does
mark add <URL>Save a bookmark. -t TITLE -T tags -n NOTE -C COL --pin --rate 1..5. URL is normalized before insert; duplicates merge.
mark rm <ID|URL>Remove by numeric ID, exact URL, or unique FTS hit. -f skips the prompt.
mark edit <ID|URL>Update fields. --tags replaces; --tags-add / --tags-remove mutate incrementally.
mark lsList with filters: tag (repeatable, AND), collection, domain, pinned, dead, min rating. --sort created|updated|rating|title.
mark search <QUERY>FTS5 BM25 search across URL / title / description / notes. Supports phrases, OR, -exclude.
mark open <ID|QUERY>Launch URL via xdg-open, or --print to stdout.
mark copy <ID|QUERY>Copy URL to the system clipboard.
mark pin / mark unpin / mark tagSingle-field mutations.
mark recentNewest N bookmarks; defaults to 10.
mark statsTotals, top tags, top domains, dead-link percentage, oldest / newest.
mark domainsEvery unique domain with counts. --alpha for alphabetical.
mark backup [--format db|json|md]Timestamped snapshot. .db uses SQLite VACUUM INTO; .json / .md are portable exports.
mark restore <FILE>Restore from a backup. .db requires --replace; .json merges by default.
mark import <FILE>Netscape HTML (Firefox / Chrome / Safari / Pocket), native JSON, or CSV.
mark exportJSON / Markdown / HTML (Netscape, re-importable) / CSV.
mark initWrite default config and create the database directory. Idempotent.
mark completions <SHELL>bash / zsh / fish / elvish / powershell completion script.

Shell integration

# Fuzzy-open via fzf
mark ls --url-only | fzf | xargs xdg-open

# Filter by tag, pipe URLs into curl
mark search "privilege escalation" --tag oscp --url-only \
  | while read url; do curl -sI "$url"; done

# Rofi launcher (bind to a key in i3 / sway)
mark ls --json | jq -r '.bookmarks[] | "\(.id)\t\(.title // .url)\t\(.url)"' \
  | rofi -dmenu -i -p "bookmarks" \
  | awk -F'\t' '{print $1}' \
  | xargs mark open

# Pipe clipboard URL into mark
xclip -o | xargs mark add -T inbox

# Suggested aliases
alias mb="mark ls --url-only | fzf | xargs -I{} mark open {}"
alias mba="mark add"
alias mbs="mark search"
alias mbl="mark ls --limit 20"

Configuration

Path: ~/.config/mark/config.toml (XDG). All sections optional; defaults are applied for absent keys.

[database]
# path = "~/.local/share/mark/bookmarks.db"

[backup]
enabled       = true
# dir         = "~/.local/share/mark/backups"
max_backups   = 30

[output]
default_limit = 50

Exit codes

CodeMeaning
0Ok
2Usage error (clap)
3Runtime error — DB I/O, parse failure, or bookmark not found

Touches / Produces / Gates

Roadmap

Build from source

cd tools/mark/rust
cargo build --release                           # ./target/release/mark
cargo test                                      # 6 unit + 5 integration tests

Toolchain pin: Rust 1.85.0. Runtime dependencies: glibc (kernel 3.2+). Crate dependencies: clap, clap_complete, rusqlite (bundled SQLite + FTS5), serde, serde_json, toml, chrono, anyhow, thiserror, directories, arboard, open, url.

Release artefacts & provenance

Every signed release ships with checksum + signature sidecars plus a CycloneDX SBOM. Enterprise procurement can pin third-party components without unpacking the binary.