JSON-lines headless mode (no-tty): stream correlated statements to stdout#2
JSON-lines headless mode (no-tty): stream correlated statements to stdout#2tejusarora wants to merge 2 commits into
Conversation
…llocated
- probes/sqlite.js: extract attachStatements() — the existing correlation
(prepare/bind/step -> one frozen record per execution) behind callbacks,
shared by the TUI signal and the new headless sink. The statements
signal's behavior is unchanged (same windowed publish, same row shape).
- src/main.js: new entry — branches on typeof tty === 'undefined' per
maintainer guidance; tty -> mount(Root), headless -> JSON lines.
- src/app.jsx (was main.jsx): exports Root, no self-mount; tty.on
registrations guarded so the module is loadable on the headless path.
- src/json.js: one JSON object per completed execution on stdout
({process,pid,tid,sql,params,rows,steps,rc,total_ns,max_ns,error?});
status/errors to stderr so stdout stays pure.
- Makefile: entry src/main.js, bundle to src/index.js (plain .js so
yeet run -T can force headless).
- README: JSON output section with jq example.
|
Thanks for this, and for flagging what you couldn't run. I tested it end to end on a Linux box against latest yeet master: builds clean, the entry ladder picks up One real bug though, in the let e = cur.get(stmtId);
if (!e || e.steps > 0) e = startExec(stmtId, comm, pid, tid);
e.comm = comm; e.pid = pid; e.tid = tid;and the PR collapsed it to Other than that, looks good to me. |
The extraction dropped the 'e.steps > 0' branch that detects a bound- again cached statement (stepped but never DONE -> new execution), plus the comm/pid/tid refresh. Two executions merged into one record with the first bind set overwritten. Restored verbatim from master.
|
Great catch — and thank you for the synthetic-stream verification, that's exactly the failure mode. Confirmed against master: the BIND handler's Also appreciate you running the entry-ladder and -T checks I couldn't — good to know index.js is actually first in the ladder. |
Implements #1, following your guidance exactly: a plain-JS entry that branches on
typeof tty === "undefined", the dashboard moved behind an exportedRoot, andyeet run -T .(or piping) now streams one JSON object per completed, correlated execution on stdout — the same records the TUI renders:{"process":"python3","pid":34412,"sql":"INSERT INTO users (username) VALUES (?)","params":[{"idx":1,"type":"text","value":"alice"}],"rows":0,"steps":1,"rc":101,"total_ns":3100000,"max_ns":3100000}Design notes:
statementsproducer intoattachStatements({onStatement, onStatus, onActivity})and shared by both sinks — I avoided observing the signal headlessly since afrom()producer only runs when watched (per AGENTS.md). The signal's behavior is byte-for-byte the same (windowed publish, row shape, CAP, rate stats).tty.onregistrations in the (renamed)app.jsxare guarded so the module loads on the headless path;main.jsmounts explicitly, so the bundle no longer relies on component auto-mount and moves tosrc/index.js(needed anyway for-T).Honest caveat: I develop on macOS, so I verified the bundle builds with your exact esbuild flags but could not run yeetd/eBPF end-to-end. Two things worth a smoke test on your side: (1) that the entry ladder picks up
src/index.jsthe way it didindex.jsx, and (2) the headless path against live traffic (demo/run.sh). Happy to iterate on anything.Context for the use case: piping this into sqlsure gives live semantic checking (fan-out, join keys, sensitive columns) of every SQLite statement on a box with zero app cooperation — I'll publish that pairing once this lands.
🤖 Generated with Claude Code