SKILL.md

  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# Space Lua tripwires
 51
 52- Space Lua APIs are camelCase: `space.readPage`, not `space.read_page`.
 53- Use `return`; `print()` output is swallowed.
 54- Don't chain `:gsub()` calls. `gsub` returns `(string, count)`, so reassign each step.
 55- Don't use `query` as a variable or parameter name; it can parse as LIQ syntax.
 56- The standard Lua `utf8` library is unavailable.
 57- In LIQ, use explicit binding: `from p = ...`.
 58
 59# Operations
 60
 61```bash
 62# search (full-text, returns JSON with excerpts/scores/offsets)
 63sb eval 'silversearch.search("query", {silent=true})'
 64
 65# read
 66sb eval 'space.readPage("PageName")'
 67sb eval 'space.getPageMeta("PageName")'
 68
 69# query the object index (Space Lua Integrated Query)
 70sb query 'from p = index.tag "page" order by p.lastModified desc limit 10 select p.name'
 71
 72# find pages that link to a specific page
 73sb query 'from l = index.tag "link" where l.toPage == "TargetPage" select l.fromPage'
 74# also available through eval, but the query wrapper is more convenient when sufficient
 75sb eval 'query[[from l = index.tag "link" where l.toPage == "TargetPage" select l.fromPage]]'
 76
 77# write / create
 78sb eval 'space.writePage("PageName", "# Title\nContent")'
 79
 80# delete
 81sb eval 'space.deletePage("PageName")'
 82
 83# edit one-liner
 84sb eval 'space.writePage("Page", plainReplace(space.readPage("Page"), "typo", "fixed"))'
 85
 86# multiple replacements
 87echo '
 88local function plainReplace(str, old, new, n)
 89  old = old:gsub("[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%0")
 90  new = new:gsub("%%", "%%%%")
 91  if n == nil then
 92    return str:gsub(old, new)
 93  else
 94    return str:gsub(old, new, n)
 95  end
 96end
 97local text = space.readPage("Title")
 98text = plainReplace(text, "mistake one", "correction one", 1) -- specify the replacement limit; you usually want 1
 99text = plainReplace(text, "mistake two", "correction two", 1)
100space.writePage("Title", text)
101return "done"
102' | sb script
103
104# writing multi-line Markdown with sb script
105# Use [==[...]==] instead of [[...]] when Markdown contains wiki links
106# or templates. If the content contains ]==], use a regular quoted string
107# with \n escapes or edit existing content with plainReplace instead.
108echo '
109local text = [==[
110Some content with [[wiki links]] and ${template directives}.
111]==]
112space.writePage("PageName", text)
113return "done"
114' | sb script
115
116# If multi-line content ends with ]] immediately before the closing ]==],
117# add a newline before the close and trim it off.
118echo '
119local text = [==[
120Ends with [[Page]]
121]==]:sub(1, -2)
122space.writePage("PageName", text)
123return "done"
124' | sb script
125```
126
127# Fetching SilverBullet docs
128
129The 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:
130
131```bash
132# Fetch a specific docs page (URL-encode spaces and slashes)
133sb eval 'net.readURI("https://silverbullet.md/.fs/Space%20Lua.md")'
134
135# List all docs pages to find what you need
136sb script <<'EOF'
137local result = http.request("https://silverbullet.md/.fs", {
138  headers = { ["X-Sync-Mode"] = "true" }
139})
140local names = {}
141for _, p in ipairs(result.body) do
142  table.insert(names, p.name)
143end
144return table.concat(names, "\n")
145EOF
146```
147
148URL-encode page names (`%20` for spaces, `%2F` for `/`). The page listing is always current, so no hardcoded index is needed.
149
150---
151
152If `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`.