1---
2name: using-silverbullet
3description: Manages notes in SilverBullet/SB via the CLI and Space Lua. Use when the user mentions notes, SilverBullet, pages, journal, tasks in their space, or asks to search/read/write/edit notes.
4license: GPL-3.0-or-later
5metadata:
6 author: Amolith <amolith@secluded.site>
7---
8
9SilverBullet is a self-hosted Markdown note-taking app with a Lua scripting layer. Do not write top-level `# Titles`; SilverBullet puts the page title at the top of the page and `Title\n\n# Title` is redundant.
10
11# CLI
12
13`sb` executes Lua _on_ the remote SilverBullet instance. Use `sb --help` to explore available commands.
14
15- `sb eval '<expr>'` — single expression, prints its return value
16- `sb script` — multi-statement script from file (`-f`), inline argument, or stdin; use `return` for output (`print` is swallowed)
17- `sb query '<sliq-expression>'` — runs Space Lua Integrated Query directly (wraps in `query[[...]]` for you)
18
19```bash
20# expression
21sb eval 'space.readPage("index")'
22
23# multi-statement via stdin
24echo '
25local text = space.readPage("index")
26text = string.gsub(text, "old", "new")
27space.writePage("index", text)
28return "done"
29' | sb script
30```
31
32# Editing pages with plainReplace
33
34`string.gsub` treats the search string as a **Lua pattern**, not plain text. Characters like `-`, `.`, `%`, and so on are magic and silently break replacements. **Always use the `plainReplace` helper** instead of raw `string.gsub` for find/replace on page content:
35
36```lua
37function plainReplace(str, old, new, n)
38 old = old:gsub("[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%0")
39 new = new:gsub("%%", "%%%%")
40 if n == nil then
41 return str:gsub(old, new)
42 else
43 return str:gsub(old, new, n)
44 end
45end
46```
47
48Usage: `plainReplace(text, "arm64-v8a", "x86_64", 1)`
49
50# Operations
51
52```bash
53# search (full-text, returns JSON with excerpts/scores/offsets)
54sb eval 'silversearch.search("query", {silent=true})'
55
56# read
57sb eval 'space.readPage("PageName")'
58sb eval 'space.getPageMeta("PageName")'
59
60# query the object index (Space Lua Integrated Query)
61sb query 'from p = index.tag "page" order by p.lastModified desc limit 10 select p.name'
62
63# find pages that link to a specific page
64sb query 'from l = index.tag "link" where l.toPage == "TargetPage" select l.fromPage'
65# also available through eval, but the query wrapper is more convenient when sufficient
66sb eval 'query[[from l = index.tag "link" where l.toPage == "TargetPage" select l.fromPage]]'
67
68# write / create
69sb eval 'space.writePage("PageName", "# Title\nContent")'
70
71# delete
72sb eval 'space.deletePage("PageName")'
73
74# edit one-liner
75sb eval 'space.writePage("Page", plainReplace(space.readPage("Page"), "typo", "fixed"))'
76
77# multiple replacements
78echo '
79local function plainReplace(str, old, new, n)
80 old = old:gsub("[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%0")
81 new = new:gsub("%%", "%%%%")
82 if n == nil then
83 return str:gsub(old, new)
84 else
85 return str:gsub(old, new, n)
86 end
87end
88local text = space.readPage("Title")
89text = plainReplace(text, "mistake one", "correction one", 1) -- specify the replacement limit; you usually want 1
90text = plainReplace(text, "mistake two", "correction two", 1)
91space.writePage("Title", text)
92return "done"
93' | sb script
94
95# writing multi-line markdown with sb script
96# Use long-string delimiters [==[...]==] instead of [[...]] when the
97# markdown itself contains [[ or ]], which would prematurely close a
98# plain Lua long string and cause syntax errors.
99echo '
100local text = [==[
101Some content with [[wiki links]] and ${template directives}.
102]==]
103space.writePage("PageName", text)
104return "done"
105' | sb script
106```
107
108# Fetching SilverBullet docs
109
110The official SilverBullet instance at `silverbullet.md` serves raw Markdown pages. When you need details on any Space Lua feature, API, or concept, fetch the docs directly:
111
112```bash
113# Fetch a specific docs page (URL-encode spaces and slashes)
114sb eval 'net.readURI("https://silverbullet.md/.fs/Space%20Lua.md")'
115
116# List all docs pages to find what you need
117sb script <<'EOF'
118local result = http.request("https://silverbullet.md/.fs", {
119 headers = { ["X-Sync-Mode"] = "true" }
120})
121local names = {}
122for _, p in ipairs(result.body) do
123 table.insert(names, p.name)
124end
125return table.concat(names, "\n")
126EOF
127```
128
129URL-encode page names (`%20` for spaces, `%2F` for `/`). The page listing is always current, so no hardcoded index is needed.
130
131---
132
133If `sb` is unavailable when you run it, fetch the contents of https://silverbullet.md/.fs/CLI.md to read the installation instructions yourself and link the user to https://silverbullet.md/CLI so they can read it too. If they use mise.jdx.dev, the line to add to their global config is `"github:silverbulletmd/silverbullet" = "latest"` and the binary they want is `sb`.