| name | dbg |
| description | Debug Node.js processes and automate browser pages using the dbg stateless CLI debugger. Use when investigating runtime bugs, inspecting variables and call stacks, analyzing object state, or automating browser testing. Every call is one command in, one response out — built for AI agents and automation. |
| context | fork |
| license | MIT |
| metadata | {"author":"douglance","version":"0.4.0"} |
dbg - Stateless Debugger & Browser Automation
Debug Node.js processes and native apps programmatically. Every dbg invocation is stateless: one command in, one response out, exit. A background daemon holds the CDP/DAP connection between calls.
When to Use
- User reports a bug or unexpected behavior in Node.js code
- Need to inspect runtime state, variables, or call stacks
- Investigating why code fails at a specific line
- Analyzing object properties or prototype chains
- Diagnosing slow CDP calls or flaky connections
- Need post-hoc analysis of debugger session history
- Browser debugging: console errors, network failures, DOM inspection
- Browser automation: navigate, click, type, screenshot
- Performance testing: network throttling, device emulation, coverage
- API mocking: intercept network requests with custom responses
Commands
Start a Session
dbg open <port|host:port>
dbg run "node <file>"
Set Breakpoints
dbg b <file>:<line>
dbg b <file>:<line> --if "<cond>"
dbg bl
dbg db <id>
Taps (Logpoints)
A tap is a never-pausing conditioned breakpoint that logs an expression each
time the line runs — instrument code without editing it or stopping execution.
Works on browser AND node (V8 inspector) sessions.
dbg tap add src/Cart.tsx:42 "user.id"
dbg tap add app.js:3 "doubled" --url "app\\.js"
dbg tap list
dbg tap hits <id> --tail 20
dbg tap rm <id>
dbg q "SELECT ts, value FROM tap_hits WHERE tap_id = <id>"
tap add echoes the RESOLVED location ("armed on :") or "arms on
script load" when no matching script is loaded yet — never silently pending. The
__dbg_tap: console sentinel is routed to tap_hits and suppressed from the
user-facing console, after consoleDelta, dbg why, and the timeline.
Flows (Record and Replay)
Flows record user actions on the active visual recorder page and replay them
with trusted CDP input, per-step readiness gates, captures, console verdicts,
and diff-vs-last-run data.
dbg record http://localhost:3000
dbg flow record checkout
dbg flow stop
dbg flow run checkout --step-timeout 5000
dbg flow list
dbg flow show checkout
dbg q "SELECT * FROM flow_run_steps"
Selector capture prefers unique #id, then [data-testid], then a unique
tag/class selector, then a full nth-of-type fallback path.
Scroll-only flows are valid: recording a user scroll or window.scrollTo(...)
persists a scroll step and replays it against static file:// reports as
well as app pages.
Use dbg for CDP-backed debugger state, flight-recorder verdicts, SQL tables,
and replayable flows. The CLI package installs @douglance/play as a normal
dependency, so general Playwright-runner workflows with durable named sessions,
accessibility snapshots, and smart selector helpers are available without
depending on ad hoc global setup.
Control Execution
dbg c
dbg n
dbg s
dbg o
dbg pause
dbg status
Inspect State (SQL)
dbg q "SELECT * FROM frames"
dbg q "SELECT name, type, value FROM vars WHERE frame_id = 0"
dbg q "SELECT * FROM scripts WHERE file LIKE '%<pattern>%'"
dbg q "SELECT name, value FROM props WHERE object_id = '<id>'"
dbg q "SELECT * FROM console"
dbg q "SELECT * FROM exceptions"
SQL syntax: SELECT [cols | *] FROM <table> [WHERE ...] [ORDER BY col [ASC|DESC]] [LIMIT n]
WHERE operators: =, !=, <, >, <=, >=, LIKE, AND, OR, ()
Evaluate and View Source
dbg e "<expression>"
dbg src
dbg src <file> <start> <end>
Diagnostics
dbg trace
dbg trace 50
dbg health
dbg reconnect
Session Lifecycle
dbg restart
dbg close
Multi-Session (Frontend + Backend)
dbg run "node server.ts"
dbg open 9229 be
dbg open 9222 fe --type page
dbg b server.ts:42 --session be
dbg navigate "http://localhost:3000/login" --session fe
dbg q "SELECT * FROM console WHERE type = 'error'" --session fe
dbg q "SELECT * FROM vars" --session be
Browser Navigation
dbg navigate <url>
dbg navigate reload
dbg navigate back
dbg navigate forward
Browser Interaction
dbg click "<selector>"
dbg type "<selector>" "<text>"
dbg select "<selector>" "<value>"
dbg screenshot
dbg screenshot /tmp/page.png
Browser Query Tables
dbg q "SELECT method, url, status FROM network WHERE status >= 400"
dbg q "SELECT * FROM dom WHERE selector = 'h1'"
dbg q "SELECT * FROM cookies"
dbg q "SELECT key, value FROM storage WHERE type = 'local'"
dbg q "SELECT * FROM performance"
dbg q "SELECT * FROM console WHERE type = 'error'"
Network Mocking
dbg mock "/api/users" '{"users":[]}'
dbg mock "/api/data" '{}' --status 500
dbg unmock "/api/users"
dbg unmock
Device Emulation & Throttling
dbg emulate iphone-14
dbg emulate ipad
dbg emulate pixel-7
dbg emulate reset
dbg throttle 3g
dbg throttle slow-3g
dbg throttle offline
dbg throttle off
Code Coverage
dbg coverage start
dbg coverage stop
dbg q "SELECT url, used_pct FROM coverage ORDER BY used_pct ASC"
Visual Flight Recorder (record → edit → after)
dbg record http://localhost:3000
dbg record http://localhost:3000 --viewport 1280x720
dbg mark before-fix
dbg after --json
dbg after --at mark:before-fix
dbg after --perf-budget lcp=2500,cls=0.1
dbg why
dbg why "coupon"
dbg timeline
dbg replay <captureId>
dbg record --status
dbg record --stop
dbg after --json returns pair (diffPercent, diffPixels, clusters), regions
(diff clusters blamed to React component names, or tag.class on non-React pages;
causal: true when the component's file was just edited), styleChanges
(computed-style deltas like padding-top: 8px → 40px), consoleDelta /
exceptionDelta / networkDelta, and reportPath (self-contained HTML with
side-by-side / wipe / onion-skin / diff-overlay views). Add --open for humans.
Verdict sections (Plan V) — dbg after also returns:
networkDiff — requests grouped by method + normalized URL pattern (numeric
ids / uuids collapsed to :id), classified added / removed /
statusChanged / durationDelta (anchor window vs after window).
stateChanges — added/removed/changed local & sessionStorage keys
(JSON-parsed values), from the per-capture state_snapshots.
a11yNew — accessibility issues present in the after capture but not the
anchor (five rules: missing alt, control/button without name, control without
label, duplicate landmark, missing document title).
perfDelta (Plan X) — ΔLCP, ΔCLS (new layout shift during flows), new
longtask count, ΔJSHeap, Δbundle_bytes, and interaction count/max-duration.
Sourced from a buffered PerformanceObserver (LCP/CLS/longtask/paint/event)
plus a per-capture Performance.getMetrics subset (JSHeapUsedSize,
JSHeapTotalSize, Nodes, Documents, JSEventListeners, LayoutCount,
RecalcStyleCount) and a per-nav Script/Stylesheet bundle_bytes total.
nav_id increments per main-frame navigation observed by the daemon; entries
are stamped with the nav_id current at receipt. The recorder loop is HMR-alive
(not reload-heavy), so CLS/LCP are per-hard-nav — they reset only on real
document navigations. Rows land in the perf_samples table.
dbg why [substring] walks the substrate back from the target error to ranked
causes and returns a why verdict + one-line answer, e.g. "'Cannot read x of
undefined' first seen …, 2.1s after you saved src/Cart.tsx (epoch 4:
'before-fix', prompt: 'add coupon field')". Ranking = edit recency +
file-in-stack-trace bonus; plus the enclosing epoch, nearest commit, and active
agent prompt. Reads errors from the persisted event store, so it works after
record --stop (subject to the events TTL).
Each after section is individually skippable to stay fast: --skip-network,
--skip-state, --skip-a11y, --skip-perf (measured after ≈ 220ms with all
on — perfDelta is pure store reads and adds no CDP round-trip to after, so no
budget bump was needed; the only cold cost is one Performance.getMetrics call
per capture, best-effort and parallel to snapshot work).
--perf-budget lcp=2500,cls=0.1 fails (nonzero exit) when the after-side delta
breaches a budget (keys: lcp, cls, longtask, jsheap, bundle).
Deliberate Captures (shoot)
dbg shoot http://localhost:3000 --selector "#nav" --states hover,focus
dbg shoot src/Button.tsx --props '{"variant":"primary"}'
dbg shoot http://localhost:3000 --viewport mobile --full-page
dbg shoot http://localhost:3000 --out screenshots/ --name nav-check
Viewport presets: desktop (1280x800), tablet (768x1024), mobile (390x844),
or any WxH. Each state produces its own PNG under .dbg/shots/ (base name for
the default state, name@hover.png etc. for forced states); shots also land as
captures rows under session shoot. prefers-reduced-motion is emulated so
animations don't pollute pixels. For Storybook, shoot the story URL directly
(e.g. dbg shoot 'http://localhost:6006/iframe.html?id=button--primary').
Apple Device / Simulator (iOS, tvOS, watchOS, visionOS)
dbg devices
dbg devices --platform ios
dbg apps <device-id>
dbg attach com.example.myapp
dbg attach com.example.myapp --launch
dbg attach com.example.myapp --device sim:"iPhone 15" --launch
dbg attach com.example.myapp --device device:<udid> --launch
On physical devices, if attach fails without --launch, dbg automatically retries with --launch. After a --launch attach, dbg auto-continues past dyld into app code (lands at main).
Native Debug (LLDB Sessions)
dbg attach-lldb ./a.out
dbg registers
dbg memory <addr> <len>
dbg disasm
dbg disasm <addr>
Note: registers on physical devices returns a friendly error instead of crashing the session if the adapter disconnects.
Target Management
dbg targets 9222
dbg open 9222 --type page
dbg open 9222 --target <id>
Virtual Tables
Debugger State
| Table | Description | Required Filter |
|---|
frames | Call stack | — |
scopes | Scope chains | — |
vars | Variables (frame 0, skips global) | — |
this | this binding per frame | — |
props | Object properties | object_id |
proto | Prototype chain | object_id |
breakpoints | All breakpoints | — |
scripts | Loaded scripts | — |
source | Source lines | file or script_id |
console | Console messages | — |
exceptions | Thrown exceptions | — |
async_frames | Async stack traces | — |
listeners | Event listeners | object_id |
Event Log
| Table | Description |
|---|
events | Raw event log (daemon, CDP, connections) |
cdp / cdp_messages | CDP messages with direction and latency |
connections | Connection lifecycle (connect, disconnect, reconnect) |
events_stream | CDP event-stream debug view (stream/phase/severity classification, windowing, coalescing) |
Event log queries:
dbg q "SELECT direction, method, latency_ms FROM cdp ORDER BY id DESC LIMIT 20"
dbg q "SELECT method, latency_ms FROM cdp WHERE latency_ms > 100"
dbg q "SELECT ts, event, session_id FROM connections"
dbg q "SELECT ts, stream, severity, method, entity, summary FROM events_stream ORDER BY ts DESC LIMIT 120"
dbg q "SELECT ts, stream, severity, method, summary FROM events_stream WHERE include = 'errors' ORDER BY ts DESC LIMIT 80"
dbg q "SELECT ts, stream, method, summary FROM events_stream WHERE detail = 'full' AND window_ms = 5000 ORDER BY ts DESC LIMIT 200"
Note: the timeline name now refers to the unified cross-source stream (see
Unified Timeline below). The CDP event-stream view was renamed to
events_stream; its schema and behavior are unchanged.
Event-backed tables (events, cdp, connections, events_stream, timeline) can be queried even when no debug session is active.
Native Tables (LLDB/DAP Sessions)
| Table | Description | Required Filter |
|---|
registers | CPU register values | — |
memory | Process memory bytes | address, length |
disassembly | Disassembled instructions | address |
threads | Active threads | — |
modules | Loaded modules/libraries | — |
watchpoints | Hardware watchpoints | — |
signals | Signal information | — |
Browser Tables
| Table | Description | Required Filter |
|---|
network | HTTP requests/responses | — |
network_headers | Request/response headers | request_id |
network_body | Response body content | request_id |
page_events | Page lifecycle events | — |
dom | DOM elements by selector | selector |
styles | Computed CSS styles | node_id |
performance | Performance metrics | — |
cookies | Browser cookies | — |
storage | localStorage/sessionStorage | type (local/session) |
ws_frames | WebSocket messages | — |
coverage | JS/CSS code coverage | — |
Browser table queries:
dbg q "SELECT method, url, status, duration_ms FROM network"
dbg q "SELECT name, value FROM network_headers WHERE request_id = '<id>'"
dbg q "SELECT body FROM network_body WHERE request_id = '<id>'"
dbg q "SELECT node_id, tag, text FROM dom WHERE selector = '.error'"
dbg q "SELECT name, value FROM styles WHERE node_id = 42"
dbg q "SELECT name, value FROM cookies WHERE domain LIKE '%example%'"
dbg q "SELECT key, value FROM storage WHERE type = 'local'"
Recorder & Dev Tables
Every development signal is stored on one epoch-milliseconds timeline (UTC
ts INTEGER across every table), so screenshots, file edits, git commits, and
agent history all join on ts directly.
| Table | Description | Source |
|---|
captures | Screenshot capture metadata (url, hash, epoch_id, tier) | recorder store |
epochs | Epoch markers (auto on edit bursts, or named via dbg mark) | recorder store |
diffs | dbg after pixel-diff results | recorder store |
regions | Blamed diff clusters (diff_id) | recorder store |
edits | First-class file-edit stream — one row per fs-watch event (ts, path, epoch_id, session_id), tagged with the current epoch | recorder store |
state_snapshots | Per-capture local/sessionStorage dump (capture_id, kind, data JSON) | recorder store |
a11y_issues | Per-capture accessibility issues (capture_id, rule, selector, detail) | recorder store |
taps | Logpoints (id, session_id, file, line, expr, url_regex, enabled) | debug store |
tap_hits | Each tap fire (tap_id, ts, value) | debug store |
flows | Recorded user flows (id, ts, name, url, session_id) | recorder store |
flow_steps | Recorded flow actions (flow_id, idx, kind, selector, fallback_path, value) | recorder store |
flow_runs | Flow replay summaries (status, steps_total, steps_passed) | recorder store |
flow_run_steps | Per-step replay verdicts (status, capture_id, error, diff_percent) | recorder store |
commits | git log (hash, short_hash, ts, author, summary, files); default repo = cwd, override WHERE repo = '/abs' (500 most recent) | git |
agent_prompts | Claude Code prompts (ts, display, project); default-scoped to cwd, WHERE project = '<slug>' widens | ~/.claude/history.jsonl |
agent_sessions | Per-session transcript summaries (ts_first, ts_last, title, message_count) | ~/.claude/projects/<slug> |
timeline | Unified union over all of the above | union |
dbg q threads your shell's cwd to the daemon, so commits / agent_prompts /
agent_sessions scope to your project, not the daemon's.
Unified Timeline
timeline is a single ts-ordered union (columns: ts, kind, session_id,
label, ref_id, detail). kind ∈ capture (label=url) · mark/epoch ·
edit (label=path) · error / exception (label=text) · netfail
(label=method url status) · commit (label=summary, ref_id=short_hash) ·
prompt (label=first 120 chars) · diff. It defaults to the last 24h
unless a WHERE constrains ts.
Multi-table joins, BETWEEN, GROUP BY, and aliases now run as real SQL:
queries that reference more than one table (or that the single-table engine
can't parse) are materialized into an in-memory SQLite DB and executed there.
dbg q "SELECT p.display, e.path, c.url, x.label
FROM agent_prompts p
JOIN edits e ON e.ts BETWEEN p.ts AND p.ts + 600000
JOIN captures c ON c.ts BETWEEN e.ts AND e.ts + 30000
JOIN timeline x ON x.kind = 'error' AND x.ts BETWEEN e.ts AND e.ts + 5000
ORDER BY e.ts"
dbg q "SELECT e.path, e.ts, t.label
FROM edits e
JOIN timeline t ON t.kind = 'error' AND t.ts BETWEEN e.ts AND e.ts + 5000
ORDER BY e.ts"
dbg q "SELECT c.short_hash, c.summary
FROM commits c
WHERE (SELECT COUNT(*) FROM captures cap WHERE cap.ts > c.ts) = 0"
Cold-scan costs: commits shells out to git log (~50–150ms). agent_prompts
reads one JSONL file (~100ms cold). agent_sessions scans a project's transcript
dir — ~0.7s cold on a large history, ~50ms warm (results are cached by
path+mtime+size). edits / captures / epochs / diffs are indexed store
reads (sub-ms). dbg timeline HTML now interleaves commit + prompt chips between
frame cards by ts.
Network failures ride the timeline as the netfail kind, reconstructed from
the raw events table (whose ts is wall-clock epoch-ms at receipt — not CDP's
monotonic Network.*.timestamp), so they honor the unified ts contract.
Output Format
- TOON (compact, token-efficient) by default for every command
- JSON:
dbg q "SELECT * FROM frames" --json (or --format json)
--format toon|json|yaml|md|jsonl picks the encoding explicitly
- Flow commands return a structured object (
status: paused, file, line, function)
- Exit 0 success, 1 error. Parse stdout, check exit code.
Object Drill-Down Pattern
dbg q "SELECT name, object_id FROM vars WHERE name = 'config'"
dbg q "SELECT name, type, value FROM props WHERE object_id = '<id>'"
dbg q "SELECT name, value FROM props WHERE object_id = '<child_id>'"
Example Workflow
dbg run "node app.ts"
dbg b app.ts:42
dbg c
dbg q "SELECT name, type, value FROM vars WHERE frame_id = 0"
dbg e "config.settings"
dbg q "SELECT id, function, file, line FROM frames LIMIT 5"
dbg n
dbg q "SELECT name, value FROM vars WHERE name = 'result'"
dbg close
Browser Debug Loop
dbg open 9222 --type page
dbg navigate "http://localhost:3000/login"
dbg q "SELECT type, text FROM console WHERE type = 'error'" --json
dbg q "SELECT method, status, url FROM network WHERE status >= 400" --json
dbg q "SELECT node_id, text FROM dom WHERE selector = '.error-message'" --json
dbg navigate reload
dbg q "SELECT * FROM console WHERE type = 'error'" --json
dbg screenshot /tmp/login-fixed.png
dbg close
Environment Variables
| Variable | Default | Description |
|---|
DBG_SOCK | /tmp/dbg.sock | Unix socket path for daemon communication |
DBG_EVENTS_DB | /tmp/dbg-events.db | SQLite event store path (set a persistent path to keep history across runs) |
Postmortem Timeline Workflow
export DBG_EVENTS_DB="$HOME/.dbg/history.db"
dbg q "SELECT ts, stream, severity, method, summary FROM timeline ORDER BY ts DESC LIMIT 200"
dbg q "SELECT ts, method, error FROM cdp WHERE error != '' ORDER BY ts DESC LIMIT 100"
Native Device Debug Loop
dbg devices --platform ios
dbg apps <device-id>
dbg attach com.example.myapp
dbg b ViewController.swift:42
dbg c
dbg q "SELECT name, value FROM vars WHERE frame_id = 0"
dbg n
dbg e "self.viewModel.state"
dbg close
Visual Before/After Loop (agents)
The recorder gives every code edit a visual paper trail — no window, no human:
dbg record http://localhost:3000
dbg mark attempt-1
dbg after --json
dbg record --stop
"Before" always exists retroactively: the anchor defaults to the last capture
at the newest epoch, so you can edit first and ask questions later.
Performance: sub-second after-verdicts are the design target, so poll freely.
CI-enforced budgets (test/perf.test.ts): record cold start < 4s, mark < 500ms,
after < 1.5s, timeline < 1s, shoot < 5s, mutation → capture row < 1.5s.
The dbg after --json verdict shape:
{
"ok": true,
"pair": { "name": "capture 1 → capture 3", "baseline": {"captureId": 1, "ts": 0},
"after": {"captureId": 3, "ts": 0}, "diffPercent": 27.8,
"diffPixels": 133588, "dimensionsChanged": false, "clusters": 2 },
"regions": [ { "box": {"x": 8, "y": 61, "w": 480, "h": 280},
"label": "ColorCard", "component": "ColorCard",
"file": null, "causal": true } ],
"styleChanges": [ { "selector": "div.color-card", "prop": "padding-top",
"before": "8px", "after": "40px" } ],
"consoleDelta": { "new": [ {"type": "error", "text": "boom", "ts": 0} ] },
"exceptionDelta": { "new": [] },
"networkDelta": { "failed": [] },
"reportPath": ".dbg/recordings/report.html"
}
Retention: capture history is bounded — metadata rows are kept forever, but
pixels decay oldest-first once past the budgets (200 full frames / 100MB per
session; --max-frames, --max-bytes). Decayed frames become ≤320px thumbs,
then metadata-only (captures.tier = 'full'|'thumb'|'meta'). Epoch anchors,
diff baselines, and the newest capture never decay, so dbg after anchors
stay diffable; a decayed --at anchor returns an error suggesting the nearest
full capture. Raw CDP events rows expire after 30 minutes (most-recent 50k
kept; --events-ttl). Check usage with dbg record --status (diskBytes,
fullFrames/thumbFrames/metaFrames, eventsRows).
Recorder SQL tables (join them with console, network, etc.):
dbg q "SELECT ts, changed_files, hmr_modules, epoch_id FROM captures"
dbg q "SELECT id, ts, name, auto FROM epochs"
dbg q "SELECT name, diff_percent, report_path FROM diffs"
dbg q "SELECT diff_id, component, causal FROM regions"
Tips
- Every
dbg call is independent — no session to manage
- Breakpoints persist across
dbg restart
- Use SQL WHERE clauses to filter large result sets
dbg trace shows CDP/DAP latency to diagnose slow operations
dbg health quickly verifies the target is responsive
dbg reconnect recovers from dropped websocket connections
- For browser pages, use
dbg open <port> --type page to skip Node.js targets
dbg targets <port> lists all available tabs — use --target <id> for specific ones
- Network mocks survive page reload (Fetch domain stays enabled)
- Screenshot returns base64 PNG — agents can "see" the page
- Combine DOM queries + screenshots for both structural and visual verification
- Physical device attach auto-retries with
--launch if needed
- After
--launch, dbg auto-continues past dyld to main
registers on physical devices fails gracefully (no session crash)
Success Criteria
- Root cause identified and documented
- Relevant variables, stack frames, or object state captured
- Fix validated by re-running with breakpoints at the fix point