From 3e38471469bf64b97842b5c44329ba01bd5fe25f Mon Sep 17 00:00:00 2001 From: Amolith Date: Wed, 6 May 2026 10:52:28 -0600 Subject: [PATCH] Add a SilverBullet skill --- AGENTS.md | 2 + SKILL.md | 133 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+) create mode 100644 SKILL.md diff --git a/AGENTS.md b/AGENTS.md index ffdcc105ee477db1a19da20c8f96ccf99590a649..e05a2c89e7c9b70cdf07d5a95ec11e8079c9678e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,6 +8,8 @@ SPDX-License-Identifier: CC0-1.0 SilverBullet MCP server, this repo uses jujutsu over git and imperative, kernel-style commits over Conventional Commits. +We also maintain the SilverBullet agent skill (`SKILL.md`). It and the MCP server are separate surfaces: do not make the skill reference the MCP server, and do not make MCP prose reference the skill. When changing prose in either surface, consider whether the underlying rule applies to both, and if so, update each independently. + ## Commands ```sh diff --git a/SKILL.md b/SKILL.md new file mode 100644 index 0000000000000000000000000000000000000000..58f17fdc370fc79cb05b97d0c6b34a1d71749581 --- /dev/null +++ b/SKILL.md @@ -0,0 +1,133 @@ +--- +name: using-silverbullet +description: 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. +license: GPL-3.0-or-later +metadata: + author: Amolith +--- + +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 + +`sb` executes Lua _on_ the remote SilverBullet instance. Use `sb --help` to explore available commands. + +- `sb eval ''` — 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 ''` — runs Space Lua Integrated Query directly (wraps in `query[[...]]` for you) + +```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 + +`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 +``` + +Usage: `plainReplace(text, "arm64-v8a", "x86_64", 1)` + +# Operations + +```bash +# search (full-text, returns JSON with excerpts/scores/offsets) +sb eval 'silversearch.search("query", {silent=true})' + +# read +sb eval 'space.readPage("PageName")' +sb eval 'space.getPageMeta("PageName")' + +# 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' + +# find pages that link to a specific page +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]]' + +# write / create +sb eval 'space.writePage("PageName", "# Title\nContent")' + +# delete +sb eval 'space.deletePage("PageName")' + +# edit one-liner +sb eval 'space.writePage("Page", plainReplace(space.readPage("Page"), "typo", "fixed"))' + +# multiple replacements +echo ' +local 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 +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 long-string delimiters [==[...]==] instead of [[...]] when the +# markdown itself contains [[ or ]], which would prematurely close a +# plain Lua long string and cause syntax errors. +echo ' +local text = [==[ +Some content with [[wiki links]] and ${template directives}. +]==] +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: + +```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) +end +return table.concat(names, "\n") +EOF +``` + +URL-encode page names (`%20` for spaces, `%2F` for `/`). The page listing is always current, so no hardcoded index is needed. + +--- + +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`.