# I asked Claude for a silverbullet debugger

**URL:** https://community.silverbullet.md/t/i-asked-claude-for-a-silverbullet-debugger/4401
**Category:** Tricks & Techniques
**Created:** [September 24, 2026, 7:43am UTC](https://community.silverbullet.md/t/i-asked-claude-for-a-silverbullet-debugger/4401 "2026-09-24T07:43:32Z")
**Posts on this page:** 2
**Page:** 1

<div class="post-metadata">

### Author: ![adxsoft](https://community.silverbullet.md/user_avatar/community.silverbullet.md/adxsoft/32/2789_2.png) [@adxsoft](https://community.silverbullet.md/u/adxsoft)
#### Post date: [September 24, 2026, 7:43am UTC](https://community.silverbullet.md/t/i-asked-claude-for-a-silverbullet-debugger/4401/1 "2026-09-24T07:43:32Z")

</div>

I started using claude.ai in earnest this week and I prompted for a space lua debugging solution which it gave me in a single page.

I've used the 'dbg' functions (included in the page below) successfully so far but can't guarantee the code. I am using sb 2.10. Hope others might find it useful.

Here is the single prompt I used

```auto
You are an expert on silverbullet v2.10 and later versions. You especially understand space lua functions, queries, commands, widgets, plugs etc. I want to be able to debug space lua scripts with breakpoints and examination of objects and variables. Give me some ideas and guidance

```

Cheers!

````auto
Debug helper library for Space Lua. Drop this page anywhere in your space
(a "Library/" folder is the usual convention). Everything below lives in
one `space-lua` block so it loads as a single unit.

```space-lua
-- ============================================================
-- dbg: a poor-man's debugger for Space Lua
-- ============================================================

-- Guard against wiping state when this block reloads/reindexes.
dbg = dbg or {}
dbg._log = dbg._log or {}
dbg._watches = dbg._watches or {}
dbg._counter = dbg._counter or 0
dbg._maxLog = dbg._maxLog or 200

-- ---------- inspection / pretty-printing ----------

-- Recursively render any Lua value as a readable string.
-- Handles cycles and limits depth so you don't lock up on huge tables.
local function inspectValue(value, indent, seen, depth, maxDepth)
  indent = indent or ""
  seen = seen or {}
  depth = depth or 0
  maxDepth = maxDepth or 4

  local t = type(value)

  if t == "table" then
    if seen[value] then
      return "<circular reference>"
    end
    if depth >= maxDepth then
      return "{ ...max depth reached... }"
    end
    seen[value] = true

    local parts = {}
    for k, v in pairs(value) do
      local rendered = inspectValue(v, indent .. " ", seen, depth + 1, maxDepth)
      table.insert(parts, indent .. " " .. tostring(k) .. " = " .. rendered)
    end

    seen[value] = nil

    if #parts == 0 then
      return "{}"
    end
    return "{\n" .. table.concat(parts, ",\n") .. "\n" .. indent .. "}"

  elseif t == "string" then
    return string.format("%q", value)
  else
    return tostring(value)
  end
end

-- Public: dbg.dump(value) -> pretty string
function dbg.dump(value)
  return inspectValue(value)
end

-- ---------- logging (a stand-in for "print and inspect here") ----------

-- dbg.log(label, value) logs to the browser console AND an in-memory
-- buffer you can review later, then returns `value` unchanged so you
-- can wrap any expression without altering behavior:
-- local total = dbg.log("total", price * qty)
function dbg.log(label, value)
  dbg._counter = dbg._counter + 1
  local rendered = dbg.dump(value)

  local entry = {
    seq = dbg._counter,
    label = label,
    value = rendered
  }
  table.insert(dbg._log, entry)

  -- trim buffer so it can't grow forever in a long session
  if #dbg._log > dbg._maxLog then
    table.remove(dbg._log, 1)
  end

  js.log("[dbg #" .. entry.seq .. "] " .. tostring(label) .. " = " .. rendered)

  return value
end

-- dbg.clear() resets the log buffer and watches
function dbg.clear()
  dbg._log = {}
  dbg._watches = {}
  dbg._counter = 0
  editor.flashNotification("dbg: log and watches cleared")
end

-- dbg.dumpLog() returns the whole log buffer as one string, newest last
function dbg.dumpLog()
  local lines = {}
  for _, entry in ipairs(dbg._log) do
    table.insert(lines, "#" .. entry.seq .. " " .. entry.label .. " = " .. entry.value)
  end
  if #lines == 0 then
    return "(log is empty)"
  end
  return table.concat(lines, "\n\n")
end

-- ---------- named watches (a lightweight "watch window") ----------

-- dbg.watch(name, value) records a named value you can inspect later
-- from a panel page. Returns value unchanged, same wrapping trick as log.
function dbg.watch(name, value)
  dbg._watches[name] = dbg.dump(value)
  return value
end

-- dbg.snapshot() returns a SilverBullet widget table you can drop into
-- any page as: ${dbg.snapshot()}
-- It renders current watches as a markdown table, refreshed on preview.
function dbg.snapshot()
  local rows = {}
  table.insert(rows, "| name | value |")
  table.insert(rows, "|---|---|")

  local any = false
  for name, value in pairs(dbg._watches) do
    any = true
    local safe = value:gsub("\n", " ")
    safe = safe:gsub("|", "\\|")
    table.insert(rows, "| " .. name .. " | `" .. safe .. "` |")
  end

  if not any then
    return { markdown = "_(no watches set yet — call dbg.watch(name, value) somewhere)_" }
  end

  return { markdown = table.concat(rows, "\n") }
end

-- ---------- true pausing breakpoint ----------

-- dbg.breakpoint(label, context) actually PAUSES script execution.
-- It bridges to the browser's native window.alert, which is a blocking
-- call — so Space Lua execution halts until you click OK. This is the
-- closest thing to a real breakpoint available in this environment.
--
-- WARNING: this freezes the whole browser tab's UI while the alert is
-- open, not just the script. Remove or comment out calls before leaving
-- the page in a shared/production space.
function dbg.breakpoint(label, context)
  local msg = "Breakpoint: " .. tostring(label)
  if context ~= nil then
    msg = msg .. "\n\n" .. dbg.dump(context)
  end
  js.window.alert(msg)
end

-- dbg.breakpointIf(condition, label, context) only pauses when
-- condition is true — your equivalent of a conditional breakpoint.
function dbg.breakpointIf(condition, label, context)
  if condition then
    dbg.breakpoint(label, context)
  end
end

-- ---------- commands ----------

command.define {
  name = "Debug: Show Log",
  run = function()
    editor.flashNotification(dbg.dumpLog())
  end
}

command.define {
  name = "Debug: Clear Log And Watches",
  run = function()
    dbg.clear()
  end
}

command.define {
  name = "Debug: Write Log To Page",
  run = function()
    space.writePage("Debug Log", "# Debug Log\n\n```\n" .. dbg.dumpLog() .. "\n```")
    editor.flashNotification("Written to page 'Debug Log' — open it from the page picker")
  end
}
```

## How to use it

**Inline inspection (replaces "print and check the console"):**

```
local rows = query[[from p = index.tag "task" select p]]
local total = dbg.log("task count", #rows)
```

`dbg.log` prints a pretty-formatted value to the browser dev console
*and* returns the value unchanged, so dropping it into an existing
expression never changes behavior — remove it later with a one-line
diff.

**Named watches, viewed from a separate panel page:**

```
dbg.watch("currentUser", user)
dbg.watch("filteredResults", results)
```

Then on any page (a dedicated "Debug Panel" page is handy):

```
${dbg.snapshot()}
```

This live-previews as a small markdown table of whatever was last
watched. Alt-click it or reload the page to refresh.

**A real pause, with inspection, mid-script:**

```
for _, item in ipairs(items) do
  if item.price == nil then
    dbg.breakpoint("missing price", item)
  end
  -- processing continues here once you click OK
end
```

Because `js.window.alert` is a blocking browser call, execution
genuinely halts at that line until you dismiss the dialog — you get to
read the dumped table before anything downstream runs. Use
`dbg.breakpointIf(cond, label, context)` for the conditional-breakpoint
version so you're not clicking through every iteration.

**Commands available from the palette:**
- `Debug: Show Log` — flashes the whole log buffer as a notification
- `Debug: Clear Log And Watches` — resets state between debugging sessions
- `Debug: Write Log To Page` — dumps the log buffer into a page called
  "Debug Log" for slower review (notification flashes truncate long output)

## Known limitations, honestly

- `dbg.breakpoint` freezes the entire tab, not just your script — fine
  for solo debugging, bad if you leave one in and someone else hits that
  page.
- The log buffer is in-memory per browser session; it won't survive a
  full page reload unless you also call `Debug: Write Log To Page`.
- This can't show you a call stack or let you step line-by-line — it's
  strictly "pause here and look at these values," not a real debugger.
  If SilverBullet ships native debugging tooling later, retire this.

````

---

<div class="post-metadata">

### Author: ![zef](https://community.silverbullet.md/user_avatar/community.silverbullet.md/zef/32/7_2.png) [@zef](https://community.silverbullet.md/u/zef)
#### Post date: [September 25, 2026, 6:56am UTC](https://community.silverbullet.md/t/i-asked-claude-for-a-silverbullet-debugger/4401/2 "2026-09-25T06:56:14Z")

</div>

Nice. Adding some better lua debugging support is on my roadmap as well.
