diff --git a/SKILL.md b/SKILL.md index 99a1a1b1e974a2086d4008bed4e2ca7d19ae7511..61501c4543a5456b8766d9a729133ac1f22e9737 100644 --- a/SKILL.md +++ b/SKILL.md @@ -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 -h` for full flags and edge cases. -- `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) +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 ` — manage saved space connections. Use `-s ` 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 [ref]` — list indexed objects or fetch one object by ref. For pages, the ref is the page name. +- `sb query ''` — run Space Lua Integrated Query; this wraps the argument in `query[[...]]`. +- `sb eval ''` — 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`.