@@ -8,145 +8,121 @@ metadata:
SilverBullet 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.
-# CLI
+# Prefer the high-level CLI
-`sb` executes Lua _on_ the remote SilverBullet instance. Use `sb --help` to explore available commands.
+`sb` talks to a running SilverBullet instance through the Runtime API. Prefer `get`, `describe`, and `query` before dropping into Lua. Use `sb <command> -h` for full flags and edge cases.
-- `sb eval '<expr>'` β single expression, prints its return value
-- `sb script` β multi-statement script from file (`-f`), inline argument, or stdin; use `return` for output (`print` is swallowed)
-- `sb query '<sliq-expression>'` β runs Space Lua Integrated Query directly (wraps in `query[[...]]` for you)
+Most useful commands:
-```bash
-# expression
-sb eval 'space.readPage("index")'
-
-# multi-statement via stdin
-echo '
-local text = space.readPage("index")
-text = string.gsub(text, "old", "new")
-space.writePage("index", text)
-return "done"
-' | sb script
-```
-
-# Editing pages with plainReplace
+- `sb space ls`, `sb space add`, `sb space rm <name>` β manage saved space connections. Use `-s <name>` to select a space when more than one is configured.
+- `sb get` β list indexed tag names in the space.
+- `sb describe [tag]` β show query syntax and live schemas, e.g. `sb describe page` or `sb describe task`.
+- `sb get <tag> [ref]` β list indexed objects or fetch one object by ref. For pages, the ref is the page name.
+- `sb query '<sliq-expression>'` β run Space Lua Integrated Query; this wraps the argument in `query[[...]]`.
+- `sb eval '<expression>'` β evaluate one Space Lua expression and print its return value.
+- `sb script [code]`, `sb script -f file.lua`, or stdin β run multi-statement Space Lua. The positional argument is inline code, not a filename.
+- `sb logs`, `sb logs -n 20`, `sb logs -f` β inspect or follow headless client logs.
-`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:
-
-```lua
-function plainReplace(str, old, new, n)
- old = old:gsub("[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%0")
- new = new:gsub("%%", "%%%%")
- if n == nil then
- return str:gsub(old, new)
- else
- return str:gsub(old, new, n)
- end
-end
-```
+Use `--json` for parseable output, `-o jsonl` for line-oriented pipelines, and `--text` for human-readable output. `--text` is an output format, not βpage textβ.
-Usage: `plainReplace(text, "arm64-v8a", "x86_64", 1)`
+# Common operations
-# Space Lua tripwires
+```bash
+# List recent page metadata
+sb get page --sort-by lastModified:desc --limit 10 --select name,lastModified
-- Space Lua APIs are camelCase: `space.readPage`, not `space.read_page`.
-- Use `return`; `print()` output is swallowed.
-- Don't chain `:gsub()` calls. `gsub` returns `(string, count)`, so reassign each step.
-- Don't use `query` as a variable or parameter name; it can parse as LIQ syntax.
-- The standard Lua `utf8` library is unavailable.
-- In LIQ, use explicit binding: `from p = ...`.
+# Find pages by name/prefix metadata
+sb get page --where name:contains=meeting --select name,lastModified
+sb get page --where name:startsWith=Journal/ --select name,lastModified
-# Operations
+# Fetch one page metadata object. This does NOT return the Markdown body.
+sb get page "Journal/2026-06-07" --json
-```bash
-# search (full-text, returns JSON with excerpts/scores/offsets)
-sb eval 'silversearch.search("query", {silent=true})'
+# Read the Markdown body of a page
+sb eval 'space.readPage("Journal/2026-06-07")'
-# read
-sb eval 'space.readPage("PageName")'
-sb eval 'space.getPageMeta("PageName")'
+# Full-text search; returns matches with excerpts, scores, and offsets
+sb eval 'silversearch.search("search terms", {silent=true})'
-# query the object index (Space Lua Integrated Query)
-sb query 'from p = index.tag "page" order by p.lastModified desc limit 10 select p.name'
+# Tasks: prefer get filters for routine list/filter/sort work
+sb get task --where done=false --sort-by priority:desc --limit 20
+sb get task --where due:lte=2026-06-30 --where done=false
-# find pages that link to a specific page
+# Query when get cannot express the relationship
sb query 'from l = index.tag "link" where l.toPage == "TargetPage" select l.fromPage'
-# also available through eval, but the query wrapper is more convenient when sufficient
-sb eval 'query[[from l = index.tag "link" where l.toPage == "TargetPage" select l.fromPage]]'
+sb query 'from p = index.tag "page" order by p.lastModified desc limit 10 select p.name'
-# write / create
-sb eval 'space.writePage("PageName", "# Title\nContent")'
+# Write/create only when asked to change the space
+sb eval 'space.writePage("PageName", "Content without a duplicate top heading")'
-# delete
+# Delete only when explicitly asked
sb eval 'space.deletePage("PageName")'
+```
+
+`sb get` supports `--where`, `-l/--selector`, `--sort-by`, `--limit`, `--offset`, and `--select`; run `sb get -h` rather than memorising every filter operator.
+
+# Editing pages safely
-# edit one-liner
-sb eval 'space.writePage("Page", plainReplace(space.readPage("Page"), "typo", "fixed"))'
+`string.gsub` treats the search string as a Lua pattern, not plain text. Characters like `-`, `.`, and `%` are magic and can silently break replacements. Use a helper that returns only the replacement string:
-# multiple replacements
-echo '
+```lua
local function plainReplace(str, old, new, n)
old = old:gsub("[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%0")
new = new:gsub("%%", "%%%%")
+
+ local replaced
if n == nil then
- return str:gsub(old, new)
+ replaced = str:gsub(old, new)
else
- return str:gsub(old, new, n)
+ replaced = str:gsub(old, new, n)
end
+ return replaced
end
-local text = space.readPage("Title")
-text = plainReplace(text, "mistake one", "correction one", 1) -- specify the replacement limit; you usually want 1
-text = plainReplace(text, "mistake two", "correction two", 1)
-space.writePage("Title", text)
-return "done"
-' | sb script
-
-# writing multi-line Markdown with sb script
-# Use [==[...]==] instead of [[...]] when Markdown contains wiki links
-# or templates. If the content contains ]==], use a regular quoted string
-# with \n escapes or edit existing content with plainReplace instead.
-echo '
-local text = [==[
-Some content with [[wiki links]] and ${template directives}.
-]==]
-space.writePage("PageName", text)
-return "done"
-' | sb script
-
-# If multi-line content ends with ]] immediately before the closing ]==],
-# add a newline before the close and trim it off.
-echo '
-local text = [==[
-Ends with [[Page]]
-]==]:sub(1, -2)
-space.writePage("PageName", text)
-return "done"
-' | sb script
```
-# Fetching SilverBullet docs
-
-The 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:
+Use `sb script` for multi-step edits:
```bash
-# Fetch a specific docs page (URL-encode spaces and slashes)
-sb eval 'net.readURI("https://silverbullet.md/.fs/Space%20Lua.md")'
-
-# List all docs pages to find what you need
sb script <<'EOF'
-local result = http.request("https://silverbullet.md/.fs", {
- headers = { ["X-Sync-Mode"] = "true" }
-})
-local names = {}
-for _, p in ipairs(result.body) do
- table.insert(names, p.name)
+local function plainReplace(str, old, new, n)
+ old = old:gsub("[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%0")
+ new = new:gsub("%%", "%%%%")
+
+ local replaced
+ if n == nil then
+ replaced = str:gsub(old, new)
+ else
+ replaced = str:gsub(old, new, n)
+ end
+ return replaced
end
-return table.concat(names, "\n")
+
+local text = space.readPage("PageName")
+text = plainReplace(text, "old literal text", "new literal text", 1)
+space.writePage("PageName", text)
+return "done"
EOF
```
-URL-encode page names (`%20` for spaces, `%2F` for `/`). The page listing is always current, so no hardcoded index is needed.
+For multi-line Markdown literals, prefer `[==[...]==]` over `[[...]]` so wiki links and templates do not terminate the string accidentally. If the content itself contains `]==]`, use a normal quoted string with `\n` escapes or edit existing content with `plainReplace`.
----
+# Space Lua tripwires
+
+- Space Lua APIs are camelCase: `space.readPage`, not `space.read_page`.
+- Use `return`; `print()` output is swallowed.
+- Do not chain `:gsub()` calls. `gsub` returns `(string, count)`, so reassign each step.
+- Do not use `query` as a variable or parameter name; it can parse as SLIQ syntax.
+- The standard Lua `utf8` library is unavailable.
+- In SLIQ, use explicit binding: `from p = ...`.
+- Use current command names: `eval` and `script`. Old `lua`/`lua-script` aliases are hidden compatibility shims.
+
+# SilverBullet docs
+
+The official docs space serves raw Markdown pages. Fetch docs directly when you need detail on Space Lua, SLIQ, or the CLI:
+
+```bash
+curl -fsSL 'https://silverbullet.md/.fs/CLI.md'
+curl -fsSL 'https://silverbullet.md/.fs/Space%20Lua.md'
+```
-If `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`.
+URL-encode page names (`%20` for spaces, `%2F` for `/`). If `sb` is unavailable, read `https://silverbullet.md/.fs/CLI.md` yourself and link the user to `https://silverbullet.md/CLI`.