The OpenCode TUI renders tool output through a literal <text> node
(packages/tui/.../session/index.tsx, GenericTool), so a GFM table
becomes a wall of pipes. The web/desktop client passes the same output
through the markdown renderer and draws a proper grid, so a GFM table
is the most compact form there.
The previous fix emitted a markdown table and assumed the TUI would
render it. It does not. The fix: ship both. The ASCII table is space-
padded, word-wraps long cells, and stays aligned even when the TUI
shows only its collapsed 3-line preview; the markdown table follows
and is what the web/desktop client renders with a proper grid.
- New rowsToAsciiTable: cellToAscii, truncateForCell, wrapCell,
visibleWidth, padRight. NULL -> <null>, BLOB -> <BLOB nB>, Date ->
ISO, BigInt -> string. Cell width capped at 40 chars by default
(configurable). Single words longer than the cap are hard-wrapped.
- sqlite_query's format=table path now emits the ASCII table first
and the markdown table second, with a blank line between.
- README explains why the duplication and how to read both.
End-to-end verified via Code Mode (no model in the loop) after
opencode service restart picked up the new transform.
OpenCode's tool content schema (Tool.Content = TextContent | FileContent)
gives a server plugin no metadata channel — the TUI's only view of tool
output is the content string. So: format the result as a Markdown
table, which the TUI's built-in markdown renderer already displays as
a real table.
- sqlite_query gains format = 'table' (default) | 'json' | 'both'.
'table' is the new default and renders a header line, a separator
row, and one row per record with pipes/newlines/backslashes escaped.
'json' keeps the old behaviour for callers that need raw rows.
- Every tool that runs SQL echoes 'SQL: <stmt> params: [...]' as the
first line of its output, so the user sees what was executed
including the bound values.
- Every tool that runs a statement pushes context.progress() at start
and end, which surfaces in the TUI status bar.
- cellToMarkdown caps each cell at 80 chars to keep wide BLOBs from
blowing up the TUI column width.
End-to-end verified via Code Mode (no model in the loop):
- format=table renders correctly with edge cases (| in text, \n in
text, \ in text)
- format=both appends the JSON view below the table
- exec echoes the bound params as a JSON array
- write+read round-trip persists (UPDATE → SELECT sees the new value)
User asked what happens if the window is closed mid-task. Added a
'What happens if the process dies mid-task' section that tabulates
the four exit paths (TUI close, SIGTERM, SIGKILL, power loss) and
empirically confirms that anything past the last fsync'd commit is
on disk. The only ways to lose data are :memory:, an explicit
synchronous=OFF, or a network filesystem. Also corrects the earlier
'leak fd' framing — a dead process leaks no fds, the OS reclaims
them.
User asked what happens if the connection is never closed and whether
concurrent calls are safe. Adds a Lifecycle and concurrency section
to README.md covering:
- not closing only leaks fds; data is durable on commit
- 3-tier concurrency model: same service / multi-CLI same service /
multi-process, with the actual busy_timeout default (5s) and what
to do if a longer wait is needed (run PRAGMA busy_timeout via
sqlite_exec)
- :memory: semantics (gone on close, even within one process)
- NFS/SMB caveat
No code changes; current defaults (busy_timeout 5s, synchronous
FULL, locking normal) are sensible and the existing test sweep
already verified end-to-end.
Adds a single-file V2 plugin that exposes eight tools under the
`sqlite` namespace (open, close, list, query, exec, script, tables,
schema) so a model can read and write SQLite databases from a session.
- OpenCode discovers it automatically from ~/.config/opencode/plugins/.
- New runtime dependency: better-sqlite3 (installed in this repo's
node_modules so the host's module resolution picks it up).
- README documents install, copy-to-another-machine, options, and
troubleshooting; SETUP.md is a copy-paste checklist.
- Schema/options were trimmed down while authoring: the initial draft
registered a no-op SQLite function on every open as a 'future hook';
removed, it was dead code.