zapper is a local Markdown search engine. The CLI command is zap.
It indexes Markdown files, watches for changes, and returns keyword matches as full paths with line and column positions.
The crates.io package is s21g-zapper; it installs the zap binary.
Version 2.1.0 changes the remote API transport to HTTPS only:
zap serverequires--tls-certand--tls-keyzap search --remoteaccepts onlyhttps://endpoints- self-signed remote certificates are trusted with
--remote-ca-certorremotes[].ca_cert - plain HTTP remote API transport is removed because tokens are sent with each request
Version 0.2.0 adds:
- token-protected search API with
zap serve - remote API federation with
zap search --remote ... --remote-token ... --no-localfor remote-only searches- remote result paths in
<host>:/path/to/markdownform
Version 0.1.0 provides:
- Markdown indexing for
.md,.markdown, and.mdownfiles - automatic index refresh when Markdown files are added, updated, or deleted
- default include patterns for common workspace Markdown:
~/*.md,~/*/*.md, and~/*/memo/**/*.md - defaults can be changed with
~/.config/zapper/config.json --allfor indexing or watching every Markdown file under the root- literal, case-insensitive keyword search
- search results with full path, 1-based line number, 1-based character position, and line text
searchas the default command, sozap keywordworks- stdin keyword input, so
printf 'keyword' | zapworks - token-protected HTTPS search API with
zap serve - remote API federation with
zap search --remote ... --remote-token ... - a systemd watcher service template
- a
zap(1)manual page
Build an index:
zap index --root /home/vagrantBy default, index and watch focus on common workspace Markdown locations:
~/*.md
~/*/*.md
~/*/memo/**/*.md
Use --all to include every Markdown file under the root:
zap index --root /home/vagrant --allSearch:
zap search "keyword"The search subcommand is the default, so this is equivalent:
zap "keyword"search also accepts the keyword from stdin when it is piped:
printf 'keyword' | zap --limit 20
printf 'keyword' | zap search --limit 20Run the watcher in the foreground:
zap watch --root /home/vagrantServe a token-protected HTTPS search API:
ZAP_API_TOKEN='change-me' zap serve \
--bind 127.0.0.1:8765 \
--host-label host1 \
--tls-cert /path/to/host1.crt \
--tls-key /path/to/host1.keySearch another zapper API endpoint. Remote endpoints require HTTPS. For a self-signed certificate, pass the certificate as the remote CA certificate:
zap search "keyword" \
--remote https://host1:8765/search \
--remote-token 'change-me' \
--remote-ca-cert /path/to/host1.crtUse only remote results:
zap search "keyword" --no-local \
--remote https://host1:8765/search \
--remote-token 'change-me' \
--remote-ca-cert /path/to/host1.crtRemote result paths are prefixed with the remote host label:
host1:/path/to/file.md 12 8 matching line text
The watcher uses filesystem notifications by default. For the default config,
it registers watches only for the root, first-level workspace directories, and
matching memo subtrees. On this host that reduced the watch set from 49,126
directories for a full recursive home-directory watch to 163 directories.
Polling is still available as an explicit fallback:
zap watch --root /home/vagrant --poll-seconds 10Use a custom index path:
zap --index /tmp/zapper-index.json index --root .
zap --index /tmp/zapper-index.json search "keyword"Use a custom config file:
zap --config /path/to/config.json index
zap --config /path/to/config.json watchDefault config path:
~/.config/zapper/config.json
Example config:
{
"root": "/home/vagrant",
"index": "/home/vagrant/.local/share/zapper/index.json",
"all": false,
"include_patterns": [
"~/*.md",
"~/*/*.md",
"~/*/memo/**/*.md"
],
"watch_patterns": [
"~",
"~/*",
"~/*/memo",
"~/*/memo/**"
],
"api_token": "server-token-for-zap-serve",
"host_label": "this-host",
"tls_cert": "/home/vagrant/.config/zapper/this-host.crt",
"tls_key": "/home/vagrant/.config/zapper/this-host.key",
"remotes": [
{
"endpoint": "https://other-host:8765/search",
"token": "remote-token",
"host": "other-host",
"ca_cert": "/home/vagrant/.config/zapper/other-host.crt"
}
]
}include_patterns decide which Markdown files are indexed. watch_patterns
decide which directories get filesystem notification watches. Relative patterns
are resolved from root; ~/ is expanded to the current user's home directory.
api_token, host_label, tls_cert, and tls_key are used by zap serve.
remotes are searched alongside local results by zap search. Remote
endpoints must use https://host:port/search; plain HTTP remote API transport
is not supported because tokens are sent with each request.
A systemd unit template is provided at packaging/systemd/zapper.service.
The service runs:
/usr/local/bin/zap --index /home/vagrant/.local/share/zapper/index.json watch --root /home/vagrantInstall example:
cargo build --release
sudo install -m 0755 target/release/zap /usr/local/bin/zap
sudo install -m 0644 packaging/systemd/zapper.service /etc/systemd/system/zapper.service
sudo install -D -m 0644 man/zap.1 /usr/local/share/man/man1/zap.1
sudo systemctl daemon-reload
sudo systemctl enable --now zapper.serviceAfter installing the manual page:
man zapzap search keyword and zap keyword print tab-separated rows:
/full/path/file.md 12 8 matching line text
Columns are:
- full path
- 1-based line number
- 1-based character position within the line
- line text
Remote rows use <host>:/path/to/file.md in the first column.
zap serve exposes:
GET /health: unauthenticated health check, returnsokGET /search?q=KEYWORD&limit=N: token-protected JSON search
Tokens are accepted as either header:
Authorization: Bearer <token>
X-Zap-Token: <token>
Successful /search responses have this shape:
{
"host": "host1",
"matches": [
{
"path": "/path/to/file.md",
"line_number": 12,
"column": 8,
"line": "matching line text"
}
]
}The installed service on this host was checked with temporary Markdown files
under /home/vagrant, which is the configured service root.
Measured scope on this host:
default scope: 7,842 Markdown files, 163 watched directories
--all scope: 11,391 Markdown files, 49,126 watched directories
printf 'intro line\nprefix ZAPV01_CREATE_TOKEN suffix\n' > /home/vagrant/zapper-v01-watch-check.md
sleep 18
zap ZAPV01_CREATE_TOKEN --limit 5Result:
/home/vagrant/zapper-v01-watch-check.md 2 8 prefix ZAPV01_CREATE_TOKEN suffix
After replacing the file content, the old token disappeared and the new token was found at the updated line and position:
/home/vagrant/zapper-v01-watch-check.md 3 5 abc ZAPV01_UPDATE_TOKEN suffix
After deleting the file, the update token no longer appeared in search results. The service remained active with no restarts:
systemctl is-active zapper.service
systemctl show -p NRestarts --value zapper.service