@deaquinodev/querky 0.4.7 → 0.4.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +472 -581
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.tsx
4
- import { useState as useState5, useEffect as useEffect5 } from "react";
4
+ import { useState as useState6, useEffect as useEffect5 } from "react";
5
5
  import { parseArgs } from "util";
6
- import { render, Box as Box9, Text as Text9 } from "ink";
6
+ import { render, Box as Box7, Text as Text7 } from "ink";
7
7
 
8
8
  // src/db/client.ts
9
9
  import { Client } from "pg";
@@ -81,7 +81,7 @@ var SqliteDbClient = class {
81
81
  const stmt = this.db.prepare(sql);
82
82
  if (stmt.reader) {
83
83
  const rows = stmt.all();
84
- const fields = stmt.columns().map((c) => c.name);
84
+ const fields = stmt.columns().map((c2) => c2.name);
85
85
  return { fields, rows, rowCount: rows.length };
86
86
  }
87
87
  const info = stmt.run();
@@ -193,11 +193,11 @@ async function connectDsn(dsn) {
193
193
  }
194
194
 
195
195
  // src/ui/components/App.tsx
196
- import { useState as useState3, useEffect as useEffect4, useRef as useRef2, memo } from "react";
196
+ import { useState as useState4, useEffect as useEffect4, useRef as useRef2 } from "react";
197
197
  import { writeFileSync as writeFileSync5 } from "fs";
198
198
  import { homedir as homedir4 } from "os";
199
- import { join as join6 } from "path";
200
- import { Box as Box7, Text as Text7, useApp, useInput as useInput2, useStdin as useStdin2 } from "ink";
199
+ import { join as join5 } from "path";
200
+ import { Box as Box5, Static, Text as Text5, useApp, useInput as useInput2, useStdin as useStdin2 } from "ink";
201
201
 
202
202
  // src/db/query.ts
203
203
  async function runQuery(client, sql) {
@@ -741,10 +741,10 @@ var BUILTIN_COMMAND_LIST = Object.entries(COMMANDS).map(([name, cmd]) => ({
741
741
  description: cmd.description
742
742
  }));
743
743
  function getCompletions(partial, aliases = {}) {
744
- if (partial.length === 0) return BUILTIN_COMMAND_LIST.map((c) => c.name);
744
+ if (partial.length === 0) return BUILTIN_COMMAND_LIST.map((c2) => c2.name);
745
745
  const tokenLower = partial.toLowerCase();
746
746
  const candidates = [
747
- ...BUILTIN_COMMAND_LIST.map((c) => c.name),
747
+ ...BUILTIN_COMMAND_LIST.map((c2) => c2.name),
748
748
  ...Object.keys(aliases).filter((n) => !COMMANDS[n])
749
749
  ];
750
750
  return candidates.map((n) => ({ n, score: fuzzyScore(tokenLower, n.toLowerCase()) })).filter(({ score }) => score >= 0).sort((a, b) => b.score - a.score).map(({ n }) => n);
@@ -831,7 +831,7 @@ async function fetchErd(client, driver) {
831
831
  if (!tableMap.has(tableName)) tableMap.set(tableName, []);
832
832
  const cols = tableMap.get(tableName);
833
833
  const colName = String(row["column_name"] ?? "");
834
- if (cols.some((c) => c.name === colName)) continue;
834
+ if (cols.some((c2) => c2.name === colName)) continue;
835
835
  cols.push({
836
836
  name: colName,
837
837
  type: String(row["data_type"] ?? ""),
@@ -1050,7 +1050,7 @@ async function fetchSchema(client, driver) {
1050
1050
  }
1051
1051
 
1052
1052
  // src/ui/components/QueryInput.tsx
1053
- import { useEffect as useEffect2 } from "react";
1053
+ import { useEffect as useEffect2, useState as useState2 } from "react";
1054
1054
  import { Box, Text } from "ink";
1055
1055
 
1056
1056
  // src/ui/hooks/useVimInput.ts
@@ -1523,7 +1523,7 @@ function SqlLine({ text, bg }) {
1523
1523
  const tokens = tokenizeSql(text);
1524
1524
  return /* @__PURE__ */ jsx(Fragment, { children: tokens.map((tok, i) => /* @__PURE__ */ jsx(Text, { backgroundColor: bg, color: sqlTokenColor(tok.type), children: tok.text }, i)) });
1525
1525
  }
1526
- function SqlHighlightedInput({ text, cursorAt, mode, pad: pad2 }) {
1526
+ function SqlHighlightedInput({ text, cursorAt, mode, pad }) {
1527
1527
  const tokens = tokenizeSql(text);
1528
1528
  const parts = [];
1529
1529
  let pos = 0;
@@ -1557,7 +1557,7 @@ function SqlHighlightedInput({ text, cursorAt, mode, pad: pad2 }) {
1557
1557
  parts.push(/* @__PURE__ */ jsx(Text, { backgroundColor: ACCENT, color: BG, bold: true, children: " " }, "c"));
1558
1558
  }
1559
1559
  }
1560
- parts.push(/* @__PURE__ */ jsx(Text, { backgroundColor: BG, children: pad2 }, "pad"));
1560
+ parts.push(/* @__PURE__ */ jsx(Text, { backgroundColor: BG, children: pad }, "pad"));
1561
1561
  return /* @__PURE__ */ jsx(Fragment, { children: parts });
1562
1562
  }
1563
1563
  var HINTS = {
@@ -1586,7 +1586,7 @@ var HINTS = {
1586
1586
  ["/toggle-vim-mode", "ENABLE VIM"]
1587
1587
  ]
1588
1588
  };
1589
- var BUILTIN_DESC_MAP = Object.fromEntries(BUILTIN_COMMAND_LIST.map((c) => [c.name, c.description]));
1589
+ var BUILTIN_DESC_MAP = Object.fromEntries(BUILTIN_COMMAND_LIST.map((c2) => [c2.name, c2.description]));
1590
1590
  function QueryInput({ onSubmit, isLoading, onModeChange, onShellModeChange, vimEnabled = true, history = [], aliases = {}, schema }) {
1591
1591
  function handleTab(current) {
1592
1592
  if (!current.startsWith("/")) return null;
@@ -1620,8 +1620,13 @@ function QueryInput({ onSubmit, isLoading, onModeChange, onShellModeChange, vimE
1620
1620
  const isShellMode = value.startsWith("!");
1621
1621
  const isCommand = !isShellMode && value.startsWith("/");
1622
1622
  const partial = isCommand ? value.slice(1) : "";
1623
- const suggestions = isCommand ? getCompletions(partial, aliases) : !isShellMode && schema ? getSqlCompletions(value, cursorPos, schema) : [];
1623
+ const liveSuggestions = isCommand ? getCompletions(partial, aliases) : !isShellMode && schema ? getSqlCompletions(value, cursorPos, schema) : [];
1624
1624
  const sqlToken = isCommand ? "" : getCurrentToken(value, cursorPos);
1625
+ const [suggestions, setSuggestions] = useState2([]);
1626
+ useEffect2(() => {
1627
+ const t = setTimeout(() => setSuggestions(liveSuggestions), 150);
1628
+ return () => clearTimeout(t);
1629
+ }, [value, cursorPos]);
1625
1630
  const descMap = {
1626
1631
  ...BUILTIN_DESC_MAP,
1627
1632
  ...Object.fromEntries(
@@ -1644,7 +1649,7 @@ function QueryInput({ onSubmit, isLoading, onModeChange, onShellModeChange, vimE
1644
1649
  const isMultiLine = !isEmpty && !isShellMode && !isCommand && value.includes("\n");
1645
1650
  const dOff = isShellMode ? 2 : 0;
1646
1651
  const placeholder = "Type a SQL query\u2026";
1647
- const previewResult = !isMultiLine && suggestionIndex >= 0 && suggestions.length > 0 ? handleSuggestionAccept(value, cursorPos, suggestions[suggestionIndex]) : null;
1652
+ const previewResult = !isMultiLine && suggestionIndex >= 0 && liveSuggestions.length > 0 ? handleSuggestionAccept(value, cursorPos, liveSuggestions[suggestionIndex]) : null;
1648
1653
  const renderValue = previewResult?.value ?? value;
1649
1654
  const renderCursor = previewResult?.cursor ?? cursorPos;
1650
1655
  const lineWidth = innerWidth - 4;
@@ -1667,6 +1672,17 @@ function QueryInput({ onSubmit, isLoading, onModeChange, onShellModeChange, vimE
1667
1672
  0
1668
1673
  );
1669
1674
  const hintsInline = totalHintsWidth <= termWidth;
1675
+ const STRIP_VISIBLE = 4;
1676
+ const stripPrefixW = isCommand ? 1 : 0;
1677
+ const stripAvailW = termWidth - 2 - 3 - (STRIP_VISIBLE - 1) * 3 - STRIP_VISIBLE * stripPrefixW;
1678
+ const stripMaxPerItem = Math.max(6, Math.floor(stripAvailW / STRIP_VISIBLE));
1679
+ const hasSuggestions = !isEmpty && !isShellMode && !isMultiLine && suggestions.length > 0;
1680
+ const stripWinStart = hasSuggestions ? Math.min(Math.max(0, suggestionIndex - 1), Math.max(0, suggestions.length - STRIP_VISIBLE)) : 0;
1681
+ const stripWindow = hasSuggestions ? suggestions.slice(stripWinStart, stripWinStart + STRIP_VISIBLE) : [];
1682
+ const stripNavHint = hasSuggestions ? `Tab/\u2191\u2193 navigate Enter select ${suggestionIndex + 1}/${suggestions.length}` : " ";
1683
+ function truncateSugg(s) {
1684
+ return s.length > stripMaxPerItem ? s.slice(0, stripMaxPerItem - 1) + "\u2026" : s;
1685
+ }
1670
1686
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
1671
1687
  isMultiLine ? /* @__PURE__ */ jsxs(Fragment, { children: [
1672
1688
  /* @__PURE__ */ jsx(Text, { backgroundColor: BG, children: emptyLine }),
@@ -1707,131 +1723,35 @@ function QueryInput({ onSubmit, isLoading, onModeChange, onShellModeChange, vimE
1707
1723
  ] }),
1708
1724
  /* @__PURE__ */ jsx(Text, { backgroundColor: BG, children: emptyLine })
1709
1725
  ] }),
1710
- (() => {
1711
- const hasSuggestions = !isEmpty && !isShellMode && !isMultiLine && suggestions.length > 0;
1712
- const VISIBLE = 4;
1713
- const SEP_W = 3;
1714
- const ELLIPSIS_W = 3;
1715
- const prefixW = isCommand ? 1 : 0;
1716
- const availW = termWidth - 2 - ELLIPSIS_W - (VISIBLE - 1) * SEP_W - VISIBLE * prefixW;
1717
- const maxPerItem = Math.max(6, Math.floor(availW / VISIBLE));
1718
- const winStart = hasSuggestions ? Math.min(Math.max(0, suggestionIndex - 1), Math.max(0, suggestions.length - VISIBLE)) : 0;
1719
- const windowSlice = hasSuggestions ? suggestions.slice(winStart, winStart + VISIBLE) : [];
1720
- function truncate(s) {
1721
- return s.length > maxPerItem ? s.slice(0, maxPerItem - 1) + "\u2026" : s;
1722
- }
1723
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
1724
- /* @__PURE__ */ jsx(Box, { marginLeft: 2, children: hasSuggestions ? /* @__PURE__ */ jsxs(Fragment, { children: [
1725
- windowSlice.map((name, i) => {
1726
- const realIdx = winStart + i;
1727
- const selected = realIdx === suggestionIndex;
1728
- const displayName = truncate(name);
1729
- return /* @__PURE__ */ jsxs(Text, { children: [
1730
- i > 0 && /* @__PURE__ */ jsx(Text, { dimColor: true, children: " " }),
1731
- isCommand && /* @__PURE__ */ jsx(Text, { dimColor: !selected, color: selected ? ACCENT : void 0, children: "/" }),
1732
- /* @__PURE__ */ jsx(FuzzyHighlight, { text: displayName, token: isCommand ? partial : sqlToken, selected })
1733
- ] }, name);
1734
- }),
1735
- suggestions.length > VISIBLE && /* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u2026" })
1736
- ] }) : /* @__PURE__ */ jsx(Box, { flexDirection: hintsInline ? "row" : "column", children: allHints.map(([key, desc], i) => /* @__PURE__ */ jsxs(Text, { children: [
1737
- hintsInline && i > 0 && /* @__PURE__ */ jsx(Text, { dimColor: true, children: " | " }),
1738
- /* @__PURE__ */ jsx(Text, { color: ACCENT, bold: true, children: desc }),
1739
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: `: ${key}` })
1740
- ] }, desc)) }) }),
1741
- /* @__PURE__ */ jsx(Box, { marginLeft: 2, children: hasSuggestions ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
1742
- "Tab/\u2191\u2193 navigate Enter select ",
1743
- suggestionIndex + 1,
1744
- "/",
1745
- suggestions.length
1746
- ] }) : /* @__PURE__ */ jsx(Text, { children: " " }) })
1747
- ] });
1748
- })()
1726
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
1727
+ /* @__PURE__ */ jsx(Box, { marginLeft: 2, children: hasSuggestions ? /* @__PURE__ */ jsxs(Fragment, { children: [
1728
+ stripWindow.map((name, i) => {
1729
+ const realIdx = stripWinStart + i;
1730
+ const selected = realIdx === suggestionIndex;
1731
+ return /* @__PURE__ */ jsxs(Text, { children: [
1732
+ i > 0 && /* @__PURE__ */ jsx(Text, { dimColor: true, children: " " }),
1733
+ isCommand && /* @__PURE__ */ jsx(Text, { dimColor: !selected, color: selected ? ACCENT : void 0, children: "/" }),
1734
+ /* @__PURE__ */ jsx(FuzzyHighlight, { text: truncateSugg(name), token: isCommand ? partial : sqlToken, selected })
1735
+ ] }, name);
1736
+ }),
1737
+ suggestions.length > STRIP_VISIBLE && /* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u2026" })
1738
+ ] }) : /* @__PURE__ */ jsx(Box, { flexDirection: hintsInline ? "row" : "column", children: allHints.map(([key, desc], i) => /* @__PURE__ */ jsxs(Text, { children: [
1739
+ hintsInline && i > 0 && /* @__PURE__ */ jsx(Text, { dimColor: true, children: " | " }),
1740
+ /* @__PURE__ */ jsx(Text, { color: ACCENT, bold: true, children: desc }),
1741
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: `: ${key}` })
1742
+ ] }, desc)) }) }),
1743
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: stripNavHint })
1744
+ ] })
1749
1745
  ] });
1750
1746
  }
1751
1747
 
1752
1748
  // src/ui/components/QueryResult.tsx
1753
- import { useState as useState2, useEffect as useEffect3 } from "react";
1749
+ import { useState as useState3, useEffect as useEffect3 } from "react";
1754
1750
  import { Box as Box3, Text as Text3 } from "ink";
1755
1751
 
1756
1752
  // src/ui/components/Table.tsx
1757
1753
  import { Box as Box2, Text as Text2 } from "ink";
1758
1754
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
1759
- var COL_PAD = 1;
1760
- var INDIGO = "#818cf8";
1761
- var BORDER = "white";
1762
- var NULL_COLOR = "#6366f1";
1763
- var NULL_MARKER = "\u2205";
1764
- function isNull(val) {
1765
- return val === null || val === void 0;
1766
- }
1767
- function cellValue(val) {
1768
- if (isNull(val)) return NULL_MARKER;
1769
- if (val instanceof Date) return val.toISOString();
1770
- if (typeof val === "object") return JSON.stringify(val);
1771
- return String(val);
1772
- }
1773
- function colWidths(columns, rows) {
1774
- return columns.map((col) => {
1775
- const dataMax = rows.reduce(
1776
- (max, row) => Math.max(max, cellValue(row[col]).length),
1777
- 0
1778
- );
1779
- return Math.max(col.length, dataMax);
1780
- });
1781
- }
1782
- function pad(str, width) {
1783
- return str.slice(0, width).padEnd(width);
1784
- }
1785
- function hline(widths, left, mid, right) {
1786
- const segments = widths.map((w) => "\u2500".repeat(w + COL_PAD * 2));
1787
- return left + segments.join(mid) + right;
1788
- }
1789
- function ExpandedTable({ columns, rows }) {
1790
- const keyWidth = columns.reduce((max, col) => Math.max(max, col.length), 0);
1791
- return /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", children: rows.map((row, i) => /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginBottom: i < rows.length - 1 ? 1 : 0, children: [
1792
- /* @__PURE__ */ jsx2(Text2, { color: BORDER, children: `\u2500[ Record ${i + 1} ]${"\u2500".repeat(Math.max(0, keyWidth + 14 - String(i + 1).length))}` }),
1793
- columns.map((col) => /* @__PURE__ */ jsxs2(Box2, { children: [
1794
- /* @__PURE__ */ jsx2(Text2, { color: INDIGO, bold: true, children: col.padEnd(keyWidth) }),
1795
- /* @__PURE__ */ jsx2(Text2, { color: BORDER, children: " \u2502 " }),
1796
- isNull(row[col]) ? /* @__PURE__ */ jsx2(Text2, { color: NULL_COLOR, dimColor: true, children: NULL_MARKER }) : /* @__PURE__ */ jsx2(Text2, { children: cellValue(row[col]) })
1797
- ] }, col))
1798
- ] }, i)) });
1799
- }
1800
- function Table({ columns, rows, expanded = false }) {
1801
- if (expanded) return /* @__PURE__ */ jsx2(ExpandedTable, { columns, rows });
1802
- const widths = colWidths(columns, rows);
1803
- const topLine = hline(widths, "\u256D", "\u252C", "\u256E");
1804
- const midLine = hline(widths, "\u251C", "\u253C", "\u2524");
1805
- const botLine = hline(widths, "\u2570", "\u2534", "\u256F");
1806
- function renderHeaderRow(cols) {
1807
- return /* @__PURE__ */ jsxs2(Box2, { children: [
1808
- /* @__PURE__ */ jsx2(Text2, { color: BORDER, children: "\u2502" }),
1809
- cols.map((v, i) => /* @__PURE__ */ jsxs2(Box2, { children: [
1810
- /* @__PURE__ */ jsx2(Text2, { color: INDIGO, bold: true, children: " ".repeat(COL_PAD) + pad(v, widths[i]) + " ".repeat(COL_PAD) }),
1811
- /* @__PURE__ */ jsx2(Text2, { color: BORDER, children: "\u2502" })
1812
- ] }, i))
1813
- ] });
1814
- }
1815
- function renderDataRow(row) {
1816
- return /* @__PURE__ */ jsxs2(Box2, { children: [
1817
- /* @__PURE__ */ jsx2(Text2, { color: BORDER, children: "\u2502" }),
1818
- columns.map((col, i) => /* @__PURE__ */ jsxs2(Box2, { children: [
1819
- isNull(row[col]) ? /* @__PURE__ */ jsx2(Text2, { color: NULL_COLOR, dimColor: true, children: " ".repeat(COL_PAD) + pad(NULL_MARKER, widths[i]) + " ".repeat(COL_PAD) }) : /* @__PURE__ */ jsx2(Text2, { children: " ".repeat(COL_PAD) + pad(cellValue(row[col]), widths[i]) + " ".repeat(COL_PAD) }),
1820
- /* @__PURE__ */ jsx2(Text2, { color: BORDER, children: "\u2502" })
1821
- ] }, i))
1822
- ] });
1823
- }
1824
- return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
1825
- /* @__PURE__ */ jsx2(Text2, { color: BORDER, children: topLine }),
1826
- renderHeaderRow(columns),
1827
- /* @__PURE__ */ jsx2(Text2, { color: BORDER, children: midLine }),
1828
- rows.map((row, i) => /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
1829
- renderDataRow(row),
1830
- i < rows.length - 1 && /* @__PURE__ */ jsx2(Text2, { color: BORDER, children: midLine })
1831
- ] }, i)),
1832
- /* @__PURE__ */ jsx2(Text2, { color: BORDER, children: botLine })
1833
- ] });
1834
- }
1835
1755
 
1836
1756
  // src/ui/components/QueryResult.tsx
1837
1757
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
@@ -1843,90 +1763,11 @@ function ErrorBox({ message }) {
1843
1763
  line
1844
1764
  ] }, i)) });
1845
1765
  }
1846
- function formatDuration(ms) {
1847
- if (ms < 1e3) return `${ms}ms`;
1848
- return `${(ms / 1e3).toFixed(2)}s`;
1849
- }
1850
- function useTerminalWidth() {
1851
- const [width, setWidth] = useState2(process.stdout.columns ?? 80);
1852
- useEffect3(() => {
1853
- const handler = () => setWidth(process.stdout.columns ?? 80);
1854
- process.stdout.on("resize", handler);
1855
- return () => {
1856
- process.stdout.off("resize", handler);
1857
- };
1858
- }, []);
1859
- return width;
1860
- }
1861
- function QueryResult({ state, elapsed, page, pageSize }) {
1862
- const termWidth = useTerminalWidth();
1863
- if (state.status === "idle" || state.status === "running") return null;
1864
- const timing = elapsed !== null ? ` \xB7 ${formatDuration(elapsed)}` : "";
1865
- if (state.status === "error") {
1866
- return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
1867
- /* @__PURE__ */ jsx3(ErrorBox, { message: state.message }),
1868
- elapsed !== null && /* @__PURE__ */ jsx3(Text3, { color: theme.accent, dimColor: true, children: formatDuration(elapsed) })
1869
- ] });
1870
- }
1871
- const { result } = state;
1872
- const columns = result.fields;
1873
- if (result.rows.length === 0) {
1874
- return /* @__PURE__ */ jsx3(Box3, { children: /* @__PURE__ */ jsxs3(Text3, { color: theme.accent, children: [
1875
- "No rows returned (",
1876
- result.rowCount ?? 0,
1877
- " affected)",
1878
- timing
1879
- ] }) });
1880
- }
1881
- const totalRows = result.rows.length;
1882
- const totalPages = Math.ceil(totalRows / pageSize);
1883
- const currentPage = Math.min(page, totalPages - 1);
1884
- const start = currentPage * pageSize;
1885
- const displayRows = result.rows.slice(start, start + pageSize);
1886
- const isPaged = totalRows > pageSize;
1887
- const widths = colWidths(columns, displayRows);
1888
- const tableWidth = 1 + widths.reduce((sum, w) => sum + w + 3, 0);
1889
- const expanded = tableWidth > termWidth;
1890
- return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
1891
- /* @__PURE__ */ jsx3(Table, { columns, rows: displayRows, expanded }),
1892
- /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
1893
- isPaged && /* @__PURE__ */ jsxs3(Text3, { color: theme.warning, children: [
1894
- "Page ",
1895
- currentPage + 1,
1896
- " of ",
1897
- totalPages,
1898
- " \xB7 showing rows ",
1899
- start + 1,
1900
- "\u2013",
1901
- start + displayRows.length,
1902
- " of ",
1903
- totalRows,
1904
- currentPage > 0 ? " /prev" : "",
1905
- currentPage < totalPages - 1 ? " /next" : ""
1906
- ] }),
1907
- /* @__PURE__ */ jsxs3(Text3, { color: theme.accent, children: [
1908
- displayRows.length,
1909
- " row",
1910
- displayRows.length !== 1 ? "s" : "",
1911
- timing,
1912
- expanded ? " [expanded]" : ""
1913
- ] })
1914
- ] })
1915
- ] });
1916
- }
1917
1766
 
1918
1767
  // src/ui/components/Banner.tsx
1919
1768
  import { Box as Box4, Text as Text4 } from "ink";
1920
- import { readFileSync as readFileSync5 } from "fs";
1921
- import { fileURLToPath } from "url";
1922
- import { dirname, join as join5 } from "path";
1923
1769
  import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1924
- var __dirname = dirname(fileURLToPath(import.meta.url));
1925
- var pkg = { version: "unknown" };
1926
- try {
1927
- pkg = JSON.parse(readFileSync5(join5(__dirname, "../../../package.json"), "utf8"));
1928
- } catch {
1929
- }
1770
+ var version = true ? "0.4.9" : process.env.npm_package_version ?? "unknown";
1930
1771
  var LOGO = [
1931
1772
  " \u2597\u2584\u2584\u2584\u2584\u2584\u2596",
1932
1773
  "\u2590\u2591 \u25CF \u25CF \u2591\u258C",
@@ -1950,7 +1791,7 @@ function Banner({ connectionState }) {
1950
1791
  "Querky ",
1951
1792
  /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
1952
1793
  "v",
1953
- pkg.version
1794
+ version
1954
1795
  ] })
1955
1796
  ] }),
1956
1797
  /* @__PURE__ */ jsx4(Text4, { children: " " }),
@@ -1975,56 +1816,40 @@ function Banner({ connectionState }) {
1975
1816
  ] });
1976
1817
  }
1977
1818
 
1978
- // src/ui/components/HelpView.tsx
1979
- import { Box as Box5, Text as Text5 } from "ink";
1980
- import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
1981
- var USAGE_WIDTH = 28;
1982
- function HelpView({ data }) {
1983
- if (data.mode === "detail" && data.entry) {
1984
- const { usage, description, psqlAlias, detail, example, examples } = data.entry;
1985
- return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginTop: 1, children: [
1986
- /* @__PURE__ */ jsxs5(Text5, { bold: true, color: "white", children: [
1987
- usage,
1988
- psqlAlias ? ` ${psqlAlias}` : ""
1989
- ] }),
1990
- /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text5, { children: description }) }),
1991
- detail && /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: detail }) }),
1992
- examples && examples.length > 0 ? /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginTop: 1, children: [
1993
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "Examples:" }),
1994
- examples.map((ex, i) => /* @__PURE__ */ jsx5(Box5, { marginLeft: 2, children: /* @__PURE__ */ jsx5(Text5, { color: theme.accent, children: ex }) }, i))
1995
- ] }) : example ? /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, children: [
1996
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "Example: " }),
1997
- /* @__PURE__ */ jsx5(Text5, { color: theme.accent, children: example })
1998
- ] }) : null
1999
- ] });
2000
- }
2001
- if (data.mode === "list" && data.groups) {
2002
- return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginTop: 1, children: [
2003
- data.groups.map((group, gi) => /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginBottom: gi < data.groups.length - 1 ? 1 : 0, children: [
2004
- /* @__PURE__ */ jsx5(Text5, { bold: true, color: theme.accent, children: group.category }),
2005
- group.entries.map((entry) => /* @__PURE__ */ jsxs5(Box5, { marginLeft: 2, children: [
2006
- /* @__PURE__ */ jsx5(Text5, { color: "white", children: entry.usage.padEnd(USAGE_WIDTH) }),
2007
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: entry.description }),
2008
- entry.psqlAlias && /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
2009
- " ",
2010
- entry.psqlAlias
2011
- ] })
2012
- ] }, entry.name))
2013
- ] }, group.category)),
2014
- /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
2015
- "/help ",
2016
- "<command>",
2017
- " for more details."
2018
- ] }) })
2019
- ] });
2020
- }
2021
- return null;
2022
- }
2023
-
2024
- // src/ui/components/ErdView.tsx
2025
- import { Box as Box6, Text as Text6 } from "ink";
2026
- import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
2027
- var TABLE_COLORS = [
1819
+ // src/ui/format.ts
1820
+ var R = "\x1B[0m";
1821
+ var BOLD = "\x1B[1m";
1822
+ var DIM = "\x1B[2m";
1823
+ function fg(hex) {
1824
+ const r = parseInt(hex.slice(1, 3), 16);
1825
+ const g = parseInt(hex.slice(3, 5), 16);
1826
+ const b = parseInt(hex.slice(5, 7), 16);
1827
+ return `\x1B[38;2;${r};${g};${b}m`;
1828
+ }
1829
+ function c(hex, s) {
1830
+ return fg(hex) + s + R;
1831
+ }
1832
+ function bold(s) {
1833
+ return BOLD + s + R;
1834
+ }
1835
+ function dim(s) {
1836
+ return DIM + s + R;
1837
+ }
1838
+ function cbold(hex, s) {
1839
+ return fg(hex) + BOLD + s + R;
1840
+ }
1841
+ function cdim(hex, s) {
1842
+ return fg(hex) + DIM + s + R;
1843
+ }
1844
+ var ACCENT2 = "#818cf8";
1845
+ var HEADER_COL = "#9ca3af";
1846
+ var NULL_COL = "#6366f1";
1847
+ var INDIGO = "#818cf8";
1848
+ var AI_COL = "#a5b4fc";
1849
+ var ERROR_COL = "#ff4444";
1850
+ var WARN_COL = "#f59e0b";
1851
+ var ERD_BORDER = "#4b5563";
1852
+ var ERD_PALETTE = [
2028
1853
  "#f472b6",
2029
1854
  "#34d399",
2030
1855
  "#fb923c",
@@ -2034,30 +1859,179 @@ var TABLE_COLORS = [
2034
1859
  "#fbbf24",
2035
1860
  "#2dd4bf"
2036
1861
  ];
2037
- var BORDER2 = "#4b5563";
2038
- var PAD = 1;
2039
- var GAP = 2;
2040
- var FK_PREFIX = "FK \u2192 ";
2041
- function computeMetrics(table) {
2042
- const nameW = Math.max(2, ...table.columns.map((c) => c.name.length));
2043
- const typeW = Math.max(4, ...table.columns.map((c) => c.type.length));
2044
- const keyW = Math.max(
2045
- 2,
2046
- ...table.columns.map((c) => {
2047
- if (c.isPk) return 2;
2048
- if (c.fkTable) return FK_PREFIX.length + c.fkTable.length;
2049
- return 0;
2050
- })
2051
- );
2052
- const totalW = 4 + 3 * PAD * 2 + nameW + typeW + keyW;
2053
- return { nameW, typeW, keyW, totalW };
1862
+ var NULL_MARKER = "\u2205";
1863
+ var COL_PAD = 1;
1864
+ var SP = " ".repeat(COL_PAD);
1865
+ function fmtHeader(label, query) {
1866
+ const dot = cbold(ACCENT2, "\u25CF ");
1867
+ if (query.includes("\n")) {
1868
+ const lines = query.split("\n").map((l) => cbold(HEADER_COL, " " + l));
1869
+ return dot + cbold(HEADER_COL, label + ":") + "\n" + lines.join("\n");
1870
+ }
1871
+ return dot + cbold(HEADER_COL, `${label}(${query})`);
1872
+ }
1873
+ function fmtError(message) {
1874
+ return message.split("\n").map(
1875
+ (line, i) => cbold(ERROR_COL, (i === 0 ? "\u2717 " : " ") + line)
1876
+ ).join("\n");
1877
+ }
1878
+ function fmtOk(text) {
1879
+ return c(ACCENT2, "\u2713 ") + text;
1880
+ }
1881
+ function isNull(v) {
1882
+ return v === null || v === void 0;
1883
+ }
1884
+ function cellStr(v) {
1885
+ if (isNull(v)) return NULL_MARKER;
1886
+ if (v instanceof Date) return v.toISOString();
1887
+ if (typeof v === "object") return JSON.stringify(v);
1888
+ return String(v);
1889
+ }
1890
+ function colWidths2(columns, rows) {
1891
+ return columns.map((col) => {
1892
+ const dataMax = rows.reduce((mx, row) => Math.max(mx, cellStr(row[col]).length), 0);
1893
+ return Math.max(col.length, dataMax);
1894
+ });
1895
+ }
1896
+ function hline(widths, l, m, r) {
1897
+ return l + widths.map((w) => "\u2500".repeat(w + COL_PAD * 2)).join(m) + r;
1898
+ }
1899
+ function fmtTable(columns, rows) {
1900
+ const widths = colWidths2(columns, rows);
1901
+ const top = hline(widths, "\u256D", "\u252C", "\u256E");
1902
+ const mid = hline(widths, "\u251C", "\u253C", "\u2524");
1903
+ const bot = hline(widths, "\u2570", "\u2534", "\u256F");
1904
+ function headerRow() {
1905
+ const cells = columns.map(
1906
+ (col, i) => cbold(INDIGO, SP + col.slice(0, widths[i]).padEnd(widths[i]) + SP)
1907
+ );
1908
+ return "\u2502" + cells.join("\u2502") + "\u2502";
1909
+ }
1910
+ function dataRow(row) {
1911
+ const cells = columns.map((col, i) => {
1912
+ const v = cellStr(row[col]);
1913
+ const cell = SP + v.slice(0, widths[i]).padEnd(widths[i]) + SP;
1914
+ return isNull(row[col]) ? cdim(NULL_COL, cell) : cell;
1915
+ });
1916
+ return "\u2502" + cells.join("\u2502") + "\u2502";
1917
+ }
1918
+ const out = [top, headerRow(), mid];
1919
+ rows.forEach((row, i) => {
1920
+ out.push(dataRow(row));
1921
+ if (i < rows.length - 1) out.push(mid);
1922
+ });
1923
+ out.push(bot);
1924
+ return out.join("\n");
1925
+ }
1926
+ function fmtExpanded(columns, rows) {
1927
+ const kw = columns.reduce((mx, c2) => Math.max(mx, c2.length), 0);
1928
+ const separator = (i) => "\u2500[ Record " + (i + 1) + " ]" + "\u2500".repeat(Math.max(0, kw + 14 - String(i + 1).length));
1929
+ const out = [];
1930
+ rows.forEach((row, i) => {
1931
+ if (i > 0) out.push("");
1932
+ out.push(dim(separator(i)));
1933
+ for (const col of columns) {
1934
+ const val = isNull(row[col]) ? cdim(NULL_COL, NULL_MARKER) : cellStr(row[col]);
1935
+ out.push(cbold(INDIGO, col.padEnd(kw)) + dim(" \u2502 ") + val);
1936
+ }
1937
+ });
1938
+ return out.join("\n");
1939
+ }
1940
+ function fmtDuration(ms) {
1941
+ return ms < 1e3 ? `${ms}ms` : `${(ms / 1e3).toFixed(2)}s`;
1942
+ }
1943
+ function fmtQueryResult(state, elapsed, page, pageSize) {
1944
+ if (state.status === "idle" || state.status === "running") return "";
1945
+ const timing = elapsed !== null ? ` \xB7 ${fmtDuration(elapsed)}` : "";
1946
+ if (state.status === "error") {
1947
+ const t = elapsed !== null ? "\n" + cdim(ACCENT2, fmtDuration(elapsed)) : "";
1948
+ return fmtError(state.message) + t;
1949
+ }
1950
+ const { result } = state;
1951
+ const { fields, rows } = result;
1952
+ if (rows.length === 0) {
1953
+ return c(ACCENT2, `No rows returned (${result.rowCount ?? 0} affected)${timing}`);
1954
+ }
1955
+ const totalRows = rows.length;
1956
+ const totalPages = Math.ceil(totalRows / pageSize);
1957
+ const currentPage = Math.min(page, totalPages - 1);
1958
+ const start = currentPage * pageSize;
1959
+ const displayRows = rows.slice(start, start + pageSize);
1960
+ const isPaged = totalRows > pageSize;
1961
+ const termWidth = process.stdout.columns ?? 80;
1962
+ const widths = colWidths2(fields, displayRows);
1963
+ const tableWidth = 1 + widths.reduce((sum, w) => sum + w + 3, 0);
1964
+ const expanded = tableWidth > termWidth;
1965
+ const table = expanded ? fmtExpanded(fields, displayRows) : fmtTable(fields, displayRows);
1966
+ const lines = [table];
1967
+ if (isPaged) {
1968
+ const nav = (currentPage > 0 ? " /prev" : "") + (currentPage < totalPages - 1 ? " /next" : "");
1969
+ lines.push(c(WARN_COL, `Page ${currentPage + 1} of ${totalPages} \xB7 rows ${start + 1}\u2013${start + displayRows.length} of ${totalRows}${nav}`));
1970
+ }
1971
+ lines.push(c(ACCENT2, `${displayRows.length} row${displayRows.length !== 1 ? "s" : ""}${timing}${expanded ? " [expanded]" : ""}`));
1972
+ return lines.join("\n");
2054
1973
  }
2055
- function groupIntoRows(metrics, termW) {
1974
+ var USAGE_WIDTH = 28;
1975
+ function fmtHelp(data) {
1976
+ if (data.mode === "detail" && data.entry) {
1977
+ const { usage, description, psqlAlias, detail, example, examples } = data.entry;
1978
+ const lines = [];
1979
+ lines.push(bold(usage) + (psqlAlias ? " " + dim(psqlAlias) : ""));
1980
+ lines.push("");
1981
+ lines.push(description);
1982
+ if (detail) {
1983
+ lines.push("");
1984
+ lines.push(dim(detail));
1985
+ }
1986
+ if (examples && examples.length > 0) {
1987
+ lines.push("");
1988
+ lines.push(dim("Examples:"));
1989
+ for (const ex of examples) lines.push(" " + c(ACCENT2, ex));
1990
+ } else if (example) {
1991
+ lines.push("");
1992
+ lines.push(dim("Example: ") + c(ACCENT2, example));
1993
+ }
1994
+ return lines.join("\n");
1995
+ }
1996
+ if (data.mode === "list" && data.groups) {
1997
+ const lines = [];
1998
+ data.groups.forEach((group, gi) => {
1999
+ if (gi > 0) lines.push("");
2000
+ lines.push(cbold(ACCENT2, group.category));
2001
+ for (const entry of group.entries) {
2002
+ const alias = entry.psqlAlias ? dim(" " + entry.psqlAlias) : "";
2003
+ lines.push(" " + entry.usage.padEnd(USAGE_WIDTH) + dim(entry.description) + alias);
2004
+ }
2005
+ });
2006
+ lines.push("");
2007
+ lines.push(dim("/help <command> for more details."));
2008
+ return lines.join("\n");
2009
+ }
2010
+ return "";
2011
+ }
2012
+ function fmtErd(data) {
2013
+ if (data.tables.length === 0) return dim("No tables found in the current schema.");
2014
+ const termW = process.stdout.columns ?? 80;
2015
+ const FK_PREFIX = "FK \u2192 ";
2016
+ const PAD = 1;
2017
+ const GAP = 2;
2018
+ const colorMap = new Map(
2019
+ data.tables.map((t, i) => [t.name, ERD_PALETTE[i % ERD_PALETTE.length]])
2020
+ );
2021
+ function metrics(t) {
2022
+ const nameW = Math.max(2, ...t.columns.map((c2) => c2.name.length));
2023
+ const typeW = Math.max(4, ...t.columns.map((c2) => c2.type.length));
2024
+ const keyW = Math.max(2, ...t.columns.map(
2025
+ (c2) => c2.isPk ? 2 : c2.fkTable ? FK_PREFIX.length + c2.fkTable.length : 0
2026
+ ));
2027
+ const totalW = 4 + 3 * PAD * 2 + nameW + typeW + keyW;
2028
+ return { nameW, typeW, keyW, totalW };
2029
+ }
2030
+ const allMetrics = data.tables.map(metrics);
2056
2031
  const rows = [];
2057
- let row = [];
2058
- let usedW = 0;
2059
- for (let i = 0; i < metrics.length; i++) {
2060
- const w = metrics[i].totalW;
2032
+ let row = [], usedW = 0;
2033
+ for (let i = 0; i < allMetrics.length; i++) {
2034
+ const w = allMetrics[i].totalW;
2061
2035
  if (row.length === 0) {
2062
2036
  row.push(i);
2063
2037
  usedW = w;
@@ -2071,120 +2045,113 @@ function groupIntoRows(metrics, termW) {
2071
2045
  }
2072
2046
  }
2073
2047
  if (row.length > 0) rows.push(row);
2074
- return rows;
2075
- }
2076
- function TableBox({ table, m, color, colorMap }) {
2077
- const { nameW, typeW, keyW, totalW } = m;
2078
- const sp = " ".repeat(PAD);
2079
- const p = (s, w) => s.slice(0, w).padEnd(w);
2080
- const top = "\u256D" + "\u2500".repeat(totalW - 2) + "\u256E";
2081
- const sep = "\u251C" + "\u2500".repeat(nameW + PAD * 2) + "\u252C" + "\u2500".repeat(typeW + PAD * 2) + "\u252C" + "\u2500".repeat(keyW + PAD * 2) + "\u2524";
2082
- const bot = "\u2570" + "\u2500".repeat(nameW + PAD * 2) + "\u2534" + "\u2500".repeat(typeW + PAD * 2) + "\u2534" + "\u2500".repeat(keyW + PAD * 2) + "\u256F";
2083
- const headerW = totalW - 4;
2084
- return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
2085
- /* @__PURE__ */ jsx6(Text6, { color: BORDER2, children: top }),
2086
- /* @__PURE__ */ jsxs6(Box6, { children: [
2087
- /* @__PURE__ */ jsx6(Text6, { color: BORDER2, children: "\u2502" }),
2088
- /* @__PURE__ */ jsxs6(Text6, { color, bold: true, children: [
2089
- sp,
2090
- p(table.name, headerW),
2091
- sp
2092
- ] }),
2093
- /* @__PURE__ */ jsx6(Text6, { color: BORDER2, children: "\u2502" })
2094
- ] }),
2095
- /* @__PURE__ */ jsx6(Text6, { color: BORDER2, children: sep }),
2096
- table.columns.map((col, i) => /* @__PURE__ */ jsxs6(Box6, { children: [
2097
- /* @__PURE__ */ jsx6(Text6, { color: BORDER2, children: "\u2502" }),
2098
- /* @__PURE__ */ jsxs6(Text6, { children: [
2099
- sp,
2100
- p(col.name, nameW),
2101
- sp
2102
- ] }),
2103
- /* @__PURE__ */ jsx6(Text6, { color: BORDER2, children: "\u2502" }),
2104
- /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
2105
- sp,
2106
- p(col.type, typeW),
2107
- sp
2108
- ] }),
2109
- /* @__PURE__ */ jsx6(Text6, { color: BORDER2, children: "\u2502" }),
2110
- col.isPk ? /* @__PURE__ */ jsxs6(Text6, { bold: true, children: [
2111
- sp,
2112
- p("PK", keyW),
2113
- sp
2114
- ] }) : col.fkTable ? /* @__PURE__ */ jsxs6(Fragment3, { children: [
2115
- /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
2116
- sp,
2117
- FK_PREFIX
2118
- ] }),
2119
- /* @__PURE__ */ jsxs6(Text6, { color: colorMap.get(col.fkTable) ?? color, children: [
2120
- p(col.fkTable, keyW - FK_PREFIX.length),
2121
- sp
2122
- ] })
2123
- ] }) : /* @__PURE__ */ jsxs6(Text6, { children: [
2124
- sp,
2125
- " ".repeat(keyW),
2126
- sp
2127
- ] }),
2128
- /* @__PURE__ */ jsx6(Text6, { color: BORDER2, children: "\u2502" })
2129
- ] }, i)),
2130
- /* @__PURE__ */ jsx6(Text6, { color: BORDER2, children: bot })
2131
- ] });
2048
+ function renderTable(t, m, color) {
2049
+ const { nameW, typeW, keyW, totalW } = m;
2050
+ const sp = " ".repeat(PAD);
2051
+ const p = (s, w) => s.slice(0, w).padEnd(w);
2052
+ const bdr = (s) => c(ERD_BORDER, s);
2053
+ const tc = (s) => cbold(color, s);
2054
+ const top = bdr("\u256D" + "\u2500".repeat(totalW - 2) + "\u256E");
2055
+ const headerW = totalW - 4;
2056
+ const headerLine = bdr("\u2502") + tc(sp + p(t.name, headerW) + sp) + bdr("\u2502");
2057
+ const sep = bdr("\u251C" + "\u2500".repeat(nameW + PAD * 2) + "\u252C" + "\u2500".repeat(typeW + PAD * 2) + "\u252C" + "\u2500".repeat(keyW + PAD * 2) + "\u2524");
2058
+ const bot = bdr("\u2570" + "\u2500".repeat(nameW + PAD * 2) + "\u2534" + "\u2500".repeat(typeW + PAD * 2) + "\u2534" + "\u2500".repeat(keyW + PAD * 2) + "\u256F");
2059
+ const out = [top, headerLine, sep];
2060
+ for (const col of t.columns) {
2061
+ let keyPart;
2062
+ if (col.isPk) {
2063
+ keyPart = sp + bold(p("PK", keyW)) + sp;
2064
+ } else if (col.fkTable) {
2065
+ const fkColor = colorMap.get(col.fkTable) ?? color;
2066
+ keyPart = sp + dim(FK_PREFIX) + c(fkColor, p(col.fkTable, keyW - FK_PREFIX.length)) + sp;
2067
+ } else {
2068
+ keyPart = sp + " ".repeat(keyW) + sp;
2069
+ }
2070
+ out.push(
2071
+ bdr("\u2502") + sp + p(col.name, nameW) + sp + bdr("\u2502") + dim(sp + p(col.type, typeW) + sp) + bdr("\u2502") + keyPart + bdr("\u2502")
2072
+ );
2073
+ }
2074
+ out.push(bot);
2075
+ return out;
2076
+ }
2077
+ const output = [];
2078
+ for (const tableRow of rows) {
2079
+ const blocks = tableRow.map((ti) => {
2080
+ const t = data.tables[ti];
2081
+ return renderTable(t, allMetrics[ti], colorMap.get(t.name) ?? ERD_PALETTE[0]);
2082
+ });
2083
+ const height = Math.max(...blocks.map((b) => b.length));
2084
+ for (let line = 0; line < height; line++) {
2085
+ const parts = blocks.map((b, j) => {
2086
+ const l = b[line] ?? " ".repeat(allMetrics[tableRow[j]].totalW);
2087
+ return j < blocks.length - 1 ? l + " ".repeat(GAP) : l;
2088
+ });
2089
+ output.push(parts.join(""));
2090
+ }
2091
+ output.push("");
2092
+ }
2093
+ return output.join("\n").trimEnd();
2132
2094
  }
2133
- function ErdView({ data }) {
2134
- if (data.tables.length === 0) {
2135
- return /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "No tables found in the current schema." });
2095
+ function fmtAi(text, error) {
2096
+ const lines = [cbold(ACCENT2, "Explanation:"), ""];
2097
+ if (error) {
2098
+ lines.push(fmtError(error));
2099
+ } else {
2100
+ lines.push(c(AI_COL, text));
2136
2101
  }
2137
- const termW = process.stdout.columns ?? 80;
2138
- const metrics = data.tables.map(computeMetrics);
2139
- const colorMap = new Map(
2140
- data.tables.map((t, i) => [t.name, TABLE_COLORS[i % TABLE_COLORS.length]])
2141
- );
2142
- const rows = groupIntoRows(metrics, termW);
2143
- return /* @__PURE__ */ jsx6(Box6, { flexDirection: "column", marginTop: 1, children: rows.map((row, ri) => /* @__PURE__ */ jsx6(Box6, { flexDirection: "row", marginBottom: ri < rows.length - 1 ? 1 : 0, children: row.map((ti, j) => /* @__PURE__ */ jsx6(Box6, { marginRight: j < row.length - 1 ? GAP : 0, children: /* @__PURE__ */ jsx6(
2144
- TableBox,
2145
- {
2146
- table: data.tables[ti],
2147
- m: metrics[ti],
2148
- color: colorMap.get(data.tables[ti].name) ?? BORDER2,
2149
- colorMap
2102
+ return lines.join("\n");
2103
+ }
2104
+ function fmtEntry(entry) {
2105
+ const isShell = entry.query.startsWith("!");
2106
+ const isCommand = !isShell && (entry.query.startsWith("/") || entry.query.startsWith("\\"));
2107
+ const label = isShell ? "Shell" : isCommand ? "Command" : "Query";
2108
+ const lines = [" " + fmtHeader(label, entry.query), ""];
2109
+ if (isShell) {
2110
+ if (entry.shellOutput !== null) lines.push(" " + entry.shellOutput.replace(/\n/g, "\n "));
2111
+ } else if (entry.commandMessage) {
2112
+ if (entry.commandMessage.helpData) {
2113
+ lines.push(fmtHelp(entry.commandMessage.helpData).replace(/\n/g, "\n "));
2114
+ } else if (entry.commandMessage.ok) {
2115
+ lines.push(" " + fmtOk(entry.commandMessage.text));
2116
+ } else {
2117
+ lines.push(" " + fmtError(entry.commandMessage.text));
2150
2118
  }
2151
- ) }, ti)) }, ri)) });
2119
+ } else if (entry.erdData) {
2120
+ lines.push(" " + fmtErd(entry.erdData).replace(/\n/g, "\n "));
2121
+ } else if (entry.aiResponse || entry.aiError) {
2122
+ lines.push(" " + fmtAi(entry.aiResponse, entry.aiError).replace(/\n/g, "\n "));
2123
+ } else {
2124
+ const result = fmtQueryResult(entry.queryState, entry.elapsed, entry.page, 50);
2125
+ if (result) lines.push(" " + result.replace(/\n/g, "\n "));
2126
+ }
2127
+ lines.push("");
2128
+ return lines.join("\n");
2152
2129
  }
2153
2130
 
2154
2131
  // src/ui/components/App.tsx
2155
- import { Fragment as Fragment4, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
2156
- var PAGE_SIZE = 50;
2132
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
2157
2133
  var PLACEHOLDER2 = "#a5b4fc";
2158
- function activePageSize() {
2159
- return Math.max(3, (process.stdout.rows ?? 24) - 21);
2160
- }
2161
- function limitLines(s, n) {
2162
- const lines = s.split("\n");
2163
- if (lines.length <= n) return s;
2164
- return lines.slice(0, n).join("\n") + `
2165
- \u2026 +${lines.length - n} more lines (scroll up after next submit)`;
2166
- }
2167
2134
  var QUERY_HEADER_COLOR = "#9ca3af";
2168
2135
  function QueryHeader({ label, query }) {
2169
2136
  const isMultiLine = query.includes("\n");
2170
2137
  if (isMultiLine) {
2171
- return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
2172
- /* @__PURE__ */ jsxs7(Text7, { children: [
2173
- /* @__PURE__ */ jsx7(Text7, { color: theme.accent, bold: true, children: "\u25CF " }),
2174
- /* @__PURE__ */ jsxs7(Text7, { color: QUERY_HEADER_COLOR, bold: true, children: [
2138
+ return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
2139
+ /* @__PURE__ */ jsxs5(Text5, { children: [
2140
+ /* @__PURE__ */ jsx5(Text5, { color: theme.accent, bold: true, children: "\u25CF " }),
2141
+ /* @__PURE__ */ jsxs5(Text5, { color: QUERY_HEADER_COLOR, bold: true, children: [
2175
2142
  label,
2176
2143
  ":"
2177
2144
  ] })
2178
2145
  ] }),
2179
- query.split("\n").map((line, i) => /* @__PURE__ */ jsxs7(Text7, { color: QUERY_HEADER_COLOR, bold: true, children: [
2146
+ query.split("\n").map((line, i) => /* @__PURE__ */ jsxs5(Text5, { color: QUERY_HEADER_COLOR, bold: true, children: [
2180
2147
  " ",
2181
2148
  line
2182
2149
  ] }, i))
2183
2150
  ] });
2184
2151
  }
2185
- return /* @__PURE__ */ jsxs7(Text7, { children: [
2186
- /* @__PURE__ */ jsx7(Text7, { color: theme.accent, bold: true, children: "\u25CF " }),
2187
- /* @__PURE__ */ jsxs7(Text7, { color: QUERY_HEADER_COLOR, bold: true, children: [
2152
+ return /* @__PURE__ */ jsxs5(Text5, { children: [
2153
+ /* @__PURE__ */ jsx5(Text5, { color: theme.accent, bold: true, children: "\u25CF " }),
2154
+ /* @__PURE__ */ jsxs5(Text5, { color: QUERY_HEADER_COLOR, bold: true, children: [
2188
2155
  label,
2189
2156
  "(",
2190
2157
  query,
@@ -2192,55 +2159,31 @@ function QueryHeader({ label, query }) {
2192
2159
  ] })
2193
2160
  ] });
2194
2161
  }
2195
- var EntryView = memo(function EntryView2({ entry }) {
2196
- const showAi = entry.aiResponse !== "" || entry.aiError !== null;
2197
- const showErd = entry.erdData !== null;
2198
- const isShell = entry.query.startsWith("!");
2199
- const isCommand = !isShell && (entry.query.startsWith("/") || entry.query.startsWith("\\"));
2200
- const label = isShell ? "Shell" : isCommand ? "Command" : "Query";
2201
- return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", marginBottom: 1, paddingX: 1, children: [
2202
- /* @__PURE__ */ jsx7(QueryHeader, { label, query: entry.query }),
2203
- /* @__PURE__ */ jsx7(Box7, { marginTop: 1, flexDirection: "column", children: isShell ? entry.shellOutput !== null && /* @__PURE__ */ jsx7(Text7, { children: entry.shellOutput || "(no output)" }) : /* @__PURE__ */ jsxs7(Fragment4, { children: [
2204
- entry.commandMessage && (entry.commandMessage.helpData ? /* @__PURE__ */ jsx7(HelpView, { data: entry.commandMessage.helpData }) : entry.commandMessage.ok ? /* @__PURE__ */ jsxs7(Text7, { color: theme.accent, children: [
2205
- "\u2713 ",
2206
- entry.commandMessage.text
2207
- ] }) : /* @__PURE__ */ jsx7(ErrorBox, { message: entry.commandMessage.text })),
2208
- showErd ? /* @__PURE__ */ jsx7(ErdView, { data: entry.erdData }) : showAi ? /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
2209
- /* @__PURE__ */ jsx7(Text7, { color: theme.accent, bold: true, children: "Explanation:" }),
2210
- /* @__PURE__ */ jsx7(Box7, { flexDirection: "column", marginTop: 1, children: entry.aiError ? /* @__PURE__ */ jsx7(ErrorBox, { message: entry.aiError }) : /* @__PURE__ */ jsx7(Text7, { color: PLACEHOLDER2, children: entry.aiResponse }) })
2211
- ] }) : !entry.commandMessage && /* @__PURE__ */ jsx7(QueryResult, { state: entry.queryState, elapsed: entry.elapsed, page: entry.page, pageSize: PAGE_SIZE })
2212
- ] }) })
2213
- ] });
2214
- });
2215
2162
  function App({ connectionState, aiUrl: aiUrl2, aiModel: aiModel2, aiKey: aiKey2, onChangeDatabase }) {
2216
2163
  const { exit } = useApp();
2217
2164
  const { isRawModeSupported } = useStdin2();
2218
- const [queryState, setQueryState] = useState3({ status: "idle" });
2219
- const [lastQuery, setLastQuery] = useState3("");
2220
- const [lastSqlQuery, setLastSqlQuery] = useState3("");
2221
- const [lastResult, setLastResult] = useState3(null);
2222
- const [page, setPage] = useState3(0);
2223
- const [vimMode, setVimMode] = useState3("INSERT");
2224
- const [inputIsShell, setInputIsShell] = useState3(false);
2225
- const [elapsed, setElapsed] = useState3(null);
2226
- const [vimEnabled, setVimEnabled] = useState3(true);
2227
- const [commandMessage, setCommandMessage] = useState3(null);
2228
- const [history, setHistory] = useState3(() => loadHistory());
2229
- const [schema, setSchema] = useState3(null);
2230
- const [aiResponse, setAiResponse] = useState3("");
2231
- const [isStreaming, setIsStreaming] = useState3(false);
2232
- const [aiError, setAiError] = useState3(null);
2233
- const [shellOutput, setShellOutput] = useState3(null);
2234
- const [isShellRunning, setIsShellRunning] = useState3(false);
2235
- const [erdData, setErdData] = useState3(null);
2236
- const [isErdLoading, setIsErdLoading] = useState3(false);
2237
- const [completedEntries, setCompletedEntries] = useState3([]);
2238
- const [clearSeq, setClearSeq] = useState3(0);
2165
+ const [active, setActive] = useState4({ type: "idle" });
2166
+ const [hasHistory, setHasHistory] = useState4(false);
2167
+ const [completedEntries, setCompletedEntries] = useState4([]);
2239
2168
  const entryIdRef = useRef2(0);
2169
+ const [lastSqlQuery, setLastSqlQuery] = useState4("");
2170
+ const [lastResult, setLastResult] = useState4(null);
2171
+ const [lastResultPage, setLastResultPage] = useState4(0);
2172
+ const [vimMode, setVimMode] = useState4("INSERT");
2173
+ const [inputIsShell, setInputIsShell] = useState4(false);
2174
+ const [vimEnabled, setVimEnabled] = useState4(true);
2175
+ const [history, setHistory] = useState4(() => loadHistory());
2176
+ const [schema, setSchema] = useState4(null);
2240
2177
  const aliasScope = connectionState.status === "connected" ? makeScope(connectionState.driver, connectionState.user, connectionState.host, connectionState.database) : "";
2241
- const [aliases, setAliases] = useState3(
2178
+ const [aliases, setAliases] = useState4(
2242
2179
  () => aliasScope ? getAllAliases(aliasScope) : {}
2243
2180
  );
2181
+ function writeEntry(entry) {
2182
+ const formatted = fmtEntry(entry);
2183
+ const id = String(++entryIdRef.current);
2184
+ setCompletedEntries((prev) => [...prev, { id, formatted }]);
2185
+ setHasHistory(true);
2186
+ }
2244
2187
  function handleSaveAlias(name, query) {
2245
2188
  saveAlias(aliasScope, name, query);
2246
2189
  setAliases(getAllAliases(aliasScope));
@@ -2266,10 +2209,6 @@ function App({ connectionState, aiUrl: aiUrl2, aiModel: aiModel2, aiKey: aiKey2,
2266
2209
  process.off("SIGCONT", handleCont);
2267
2210
  };
2268
2211
  }, [isRawModeSupported]);
2269
- useEffect4(() => {
2270
- if (clearSeq === 0) return;
2271
- process.stdout.write("\x1B[3J");
2272
- }, [clearSeq]);
2273
2212
  useEffect4(() => {
2274
2213
  if (!isRawModeSupported) return;
2275
2214
  process.stdout.write("\x1B[?25l");
@@ -2288,69 +2227,59 @@ function App({ connectionState, aiUrl: aiUrl2, aiModel: aiModel2, aiKey: aiKey2,
2288
2227
  },
2289
2228
  { isActive: isRawModeSupported }
2290
2229
  );
2291
- async function handleExplain(query) {
2292
- setAiResponse("");
2293
- setAiError(null);
2294
- setIsStreaming(true);
2295
- setCommandMessage(null);
2296
- setQueryState({ status: "idle" });
2297
- try {
2298
- for await (const chunk of streamExplain(query, aiUrl2, aiModel2, aiKey2 || void 0)) {
2299
- setAiResponse((prev) => prev + chunk);
2300
- }
2301
- } catch (err) {
2302
- setAiError(err instanceof Error ? err.message : String(err));
2303
- } finally {
2304
- setIsStreaming(false);
2230
+ async function handleQuery(sql) {
2231
+ if (connectionState.status !== "connected") return;
2232
+ setLastSqlQuery(sql);
2233
+ setActive({ type: "loading", label: "Query", query: sql });
2234
+ const updated = addToHistory(history, sql);
2235
+ setHistory(updated);
2236
+ saveHistory(updated);
2237
+ const start = Date.now();
2238
+ const result = await runQuery(connectionState.client, sql);
2239
+ const elapsed = Date.now() - start;
2240
+ if (result.status === "success") {
2241
+ setLastResult(result.result);
2242
+ setLastResultPage(0);
2305
2243
  }
2244
+ writeEntry({ query: sql, commandMessage: null, queryState: result, elapsed, page: 0, aiResponse: "", aiError: null, shellOutput: null, erdData: null });
2245
+ setActive({ type: "idle" });
2306
2246
  }
2307
2247
  async function handleErd() {
2308
2248
  if (connectionState.status !== "connected") {
2309
- setCommandMessage({ ok: false, text: "Not connected to a database." });
2249
+ writeEntry({ query: "/erd", commandMessage: { ok: false, text: "Not connected." }, queryState: { status: "idle" }, elapsed: null, page: 0, aiResponse: "", aiError: null, shellOutput: null, erdData: null });
2310
2250
  return;
2311
2251
  }
2312
- setErdData(null);
2313
- setIsErdLoading(true);
2314
- setCommandMessage(null);
2315
- setQueryState({ status: "idle" });
2316
- setAiResponse("");
2317
- setAiError(null);
2252
+ setActive({ type: "loading", label: "Command", query: "/erd" });
2318
2253
  try {
2319
2254
  const data = await fetchErd(connectionState.client, connectionState.driver);
2320
- setErdData(data);
2255
+ writeEntry({ query: "/erd", commandMessage: null, queryState: { status: "idle" }, elapsed: null, page: 0, aiResponse: "", aiError: null, shellOutput: null, erdData: data });
2321
2256
  } catch (err) {
2322
- setCommandMessage({ ok: false, text: err instanceof Error ? err.message : String(err) });
2323
- } finally {
2324
- setIsErdLoading(false);
2257
+ writeEntry({ query: "/erd", commandMessage: { ok: false, text: err instanceof Error ? err.message : String(err) }, queryState: { status: "idle" }, elapsed: null, page: 0, aiResponse: "", aiError: null, shellOutput: null, erdData: null });
2325
2258
  }
2259
+ setActive({ type: "idle" });
2326
2260
  }
2327
- async function handleQuery(sql) {
2328
- if (connectionState.status !== "connected") return;
2329
- setErdData(null);
2330
- setAiResponse("");
2331
- setAiError(null);
2332
- setCommandMessage(null);
2333
- setLastQuery(sql);
2334
- setLastSqlQuery(sql);
2335
- setElapsed(null);
2336
- setPage(0);
2337
- setQueryState({ status: "running" });
2338
- const updated = addToHistory(history, sql);
2339
- setHistory(updated);
2340
- saveHistory(updated);
2341
- const start = Date.now();
2342
- const result = await runQuery(connectionState.client, sql);
2343
- setElapsed(Date.now() - start);
2344
- setQueryState(result);
2345
- if (result.status === "success") setLastResult(result.result);
2261
+ async function handleExplain(query) {
2262
+ setActive({ type: "streaming", query, text: "", error: null });
2263
+ let fullText = "";
2264
+ let error = null;
2265
+ try {
2266
+ for await (const chunk of streamExplain(query, aiUrl2, aiModel2, aiKey2 || void 0)) {
2267
+ fullText += chunk;
2268
+ setActive({ type: "streaming", query, text: fullText, error: null });
2269
+ }
2270
+ } catch (err) {
2271
+ error = err instanceof Error ? err.message : String(err);
2272
+ }
2273
+ writeEntry({ query: "/explain", commandMessage: null, queryState: { status: "idle" }, elapsed: null, page: 0, aiResponse: fullText, aiError: error, shellOutput: null, erdData: null });
2274
+ setActive({ type: "idle" });
2346
2275
  }
2347
2276
  function handleExport(format) {
2348
2277
  if (!lastResult || lastResult.rows.length === 0) {
2349
- setCommandMessage({ ok: false, text: "No results to export \u2014 run a query first." });
2278
+ writeEntry({ query: `/export ${format}`, commandMessage: { ok: false, text: "No results to export \u2014 run a query first." }, queryState: { status: "idle" }, elapsed: null, page: 0, aiResponse: "", aiError: null, shellOutput: null, erdData: null });
2350
2279
  return;
2351
2280
  }
2352
2281
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
2353
- const filename = join6(homedir4(), `q-export-${ts}.${format}`);
2282
+ const filename = join5(homedir4(), `q-export-${ts}.${format}`);
2354
2283
  try {
2355
2284
  if (format === "json") {
2356
2285
  writeFileSync5(filename, JSON.stringify(lastResult.rows, null, 2));
@@ -2366,57 +2295,36 @@ function App({ connectionState, aiUrl: aiUrl2, aiModel: aiModel2, aiKey: aiKey2,
2366
2295
  );
2367
2296
  writeFileSync5(filename, [header, ...rows].join("\n"));
2368
2297
  }
2369
- setCommandMessage({ ok: true, text: `Exported to ${filename}` });
2298
+ writeEntry({ query: `/export ${format}`, commandMessage: { ok: true, text: `Exported to ${filename}` }, queryState: { status: "idle" }, elapsed: null, page: 0, aiResponse: "", aiError: null, shellOutput: null, erdData: null });
2370
2299
  } catch (err) {
2371
- setCommandMessage({ ok: false, text: err instanceof Error ? err.message : String(err) });
2372
- }
2373
- }
2374
- function snapshotActiveEntry() {
2375
- if (lastQuery === "") return;
2376
- setCompletedEntries((prev) => [
2377
- ...prev,
2378
- {
2379
- id: entryIdRef.current++,
2380
- query: lastQuery,
2381
- commandMessage,
2382
- queryState,
2383
- elapsed,
2384
- page,
2385
- aiResponse,
2386
- aiError,
2387
- shellOutput,
2388
- erdData
2389
- }
2390
- ]);
2300
+ writeEntry({ query: `/export ${format}`, commandMessage: { ok: false, text: err instanceof Error ? err.message : String(err) }, queryState: { status: "idle" }, elapsed: null, page: 0, aiResponse: "", aiError: null, shellOutput: null, erdData: null });
2301
+ }
2391
2302
  }
2392
2303
  async function handleShell(cmd) {
2393
- setErdData(null);
2394
- setShellOutput(null);
2395
- setIsShellRunning(true);
2396
- setCommandMessage(null);
2397
- setAiResponse("");
2398
- setAiError(null);
2399
- setQueryState({ status: "idle" });
2304
+ const q = `!${cmd}`;
2305
+ setActive({ type: "loading", label: "Shell", query: q });
2400
2306
  const result = await runShell(cmd);
2401
2307
  const combined = [result.stdout, result.stderr].filter(Boolean).join("\n");
2402
- setShellOutput(combined || "(no output)");
2403
- setIsShellRunning(false);
2308
+ writeEntry({ query: q, commandMessage: null, queryState: { status: "idle" }, elapsed: null, page: 0, aiResponse: "", aiError: null, shellOutput: combined || "(no output)", erdData: null });
2309
+ setActive({ type: "idle" });
2404
2310
  }
2405
2311
  async function handleSubmit(sql) {
2406
2312
  if (sql === "/next") {
2407
- setPage((p) => p + 1);
2313
+ if (!lastResult) return;
2314
+ const p = lastResultPage + 1;
2315
+ setLastResultPage(p);
2316
+ writeEntry({ query: "/next", commandMessage: null, queryState: { status: "success", result: lastResult }, elapsed: null, page: p, aiResponse: "", aiError: null, shellOutput: null, erdData: null });
2408
2317
  return;
2409
2318
  }
2410
2319
  if (sql === "/prev") {
2411
- setPage((p) => Math.max(0, p - 1));
2320
+ if (!lastResult || lastResultPage === 0) return;
2321
+ const p = lastResultPage - 1;
2322
+ setLastResultPage(p);
2323
+ writeEntry({ query: "/prev", commandMessage: null, queryState: { status: "success", result: lastResult }, elapsed: null, page: p, aiResponse: "", aiError: null, shellOutput: null, erdData: null });
2412
2324
  return;
2413
2325
  }
2414
- snapshotActiveEntry();
2415
2326
  if (sql.startsWith("!")) {
2416
- const cmd = sql.slice(1).trim();
2417
- setLastQuery(sql);
2418
- setShellOutput(null);
2419
- void handleShell(cmd);
2327
+ void handleShell(sql.slice(1).trim());
2420
2328
  return;
2421
2329
  }
2422
2330
  if (sql.startsWith("/") || sql.startsWith("\\")) {
@@ -2441,62 +2349,45 @@ function App({ connectionState, aiUrl: aiUrl2, aiModel: aiModel2, aiKey: aiKey2,
2441
2349
  },
2442
2350
  onClear: () => {
2443
2351
  setCompletedEntries([]);
2444
- setLastQuery("");
2445
- setCommandMessage(null);
2446
- setQueryState({ status: "idle" });
2447
- setShellOutput(null);
2448
- setAiResponse("");
2449
- setAiError(null);
2450
- setElapsed(null);
2451
- setPage(0);
2452
- setClearSeq((s) => s + 1);
2352
+ setHasHistory(false);
2353
+ setActive({ type: "idle" });
2354
+ process.stdout.write("\x1B[H\x1B[2J\x1B[3J");
2453
2355
  },
2454
2356
  aliases,
2455
2357
  onSaveAlias: handleSaveAlias,
2456
2358
  onDeleteAlias: handleDeleteAlias
2457
2359
  });
2458
2360
  if (result.cleared) return;
2459
- setLastQuery(sql);
2460
- setErdData(null);
2461
- setAiResponse("");
2462
- setAiError(null);
2463
- setElapsed(null);
2464
- setPage(0);
2465
- setQueryState({ status: "idle" });
2466
- if (result.helpData) setCommandMessage({ ok: result.ok, text: "", helpData: result.helpData });
2467
- else if (result.message) setCommandMessage({ ok: result.ok, text: result.message });
2468
- else setCommandMessage(null);
2361
+ if (result.helpData) {
2362
+ writeEntry({ query: sql, commandMessage: { ok: result.ok, text: "", helpData: result.helpData }, queryState: { status: "idle" }, elapsed: null, page: 0, aiResponse: "", aiError: null, shellOutput: null, erdData: null });
2363
+ } else if (result.message) {
2364
+ writeEntry({ query: sql, commandMessage: { ok: result.ok, text: result.message }, queryState: { status: "idle" }, elapsed: null, page: 0, aiResponse: "", aiError: null, shellOutput: null, erdData: null });
2365
+ }
2469
2366
  return;
2470
2367
  }
2471
2368
  void handleQuery(sql);
2472
2369
  }
2473
- const isLoading = queryState.status === "running" || isStreaming || isShellRunning || isErdLoading;
2370
+ const isLoading = active.type !== "idle";
2474
2371
  const isConnected = connectionState.status === "connected";
2475
- const showAi = aiResponse !== "" || isStreaming || aiError !== null;
2476
- const isShellEntry = lastQuery.startsWith("!");
2477
- const isCommand = !isShellEntry && (lastQuery.startsWith("/") || lastQuery.startsWith("\\"));
2478
- const activeLabel = isShellEntry ? "Shell:" : isCommand ? "Command:" : "Query:";
2479
- return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
2480
- completedEntries.map((entry) => /* @__PURE__ */ jsx7(EntryView, { entry }, entry.id)),
2481
- /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", paddingX: 1, children: [
2482
- lastQuery === "" && /* @__PURE__ */ jsx7(Banner, { connectionState }),
2483
- lastQuery !== "" && /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", marginBottom: 2, children: [
2484
- /* @__PURE__ */ jsx7(QueryHeader, { label: activeLabel.replace(":", ""), query: lastQuery }),
2485
- /* @__PURE__ */ jsx7(Box7, { marginTop: 1, flexDirection: "column", children: isShellEntry ? isShellRunning ? /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "running\u2026" }) : /* @__PURE__ */ jsx7(Text7, { children: limitLines(shellOutput ?? "", activePageSize()) }) : /* @__PURE__ */ jsxs7(Fragment4, { children: [
2486
- commandMessage && (commandMessage.helpData ? /* @__PURE__ */ jsx7(HelpView, { data: commandMessage.helpData }) : commandMessage.ok ? /* @__PURE__ */ jsxs7(Text7, { color: theme.accent, children: [
2487
- "\u2713 ",
2488
- commandMessage.text
2489
- ] }) : /* @__PURE__ */ jsx7(ErrorBox, { message: commandMessage.text })),
2490
- isErdLoading ? /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "Fetching schema\u2026" }) : erdData ? /* @__PURE__ */ jsx7(ErdView, { data: erdData }) : showAi ? /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
2491
- /* @__PURE__ */ jsx7(Text7, { color: theme.accent, bold: true, children: "Explanation:" }),
2492
- /* @__PURE__ */ jsx7(Box7, { flexDirection: "column", marginTop: 1, children: aiError ? /* @__PURE__ */ jsx7(ErrorBox, { message: aiError }) : /* @__PURE__ */ jsxs7(Text7, { color: PLACEHOLDER2, children: [
2493
- aiResponse,
2494
- isStreaming && /* @__PURE__ */ jsx7(Text7, { color: PLACEHOLDER2, children: "\u258B" })
2495
- ] }) })
2496
- ] }) : !commandMessage && /* @__PURE__ */ jsx7(QueryResult, { state: queryState, elapsed, page, pageSize: activePageSize() })
2497
- ] }) })
2372
+ return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
2373
+ /* @__PURE__ */ jsx5(Static, { items: completedEntries, children: ({ id, formatted }) => /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(Text5, { children: formatted }) }, id) }),
2374
+ /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", paddingX: 1, children: [
2375
+ !hasHistory && active.type === "idle" && /* @__PURE__ */ jsx5(Banner, { connectionState }),
2376
+ active.type === "loading" && /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginBottom: 1, children: [
2377
+ /* @__PURE__ */ jsx5(QueryHeader, { label: active.label, query: active.query }),
2378
+ /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: " running\u2026" })
2379
+ ] }),
2380
+ active.type === "streaming" && /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginBottom: 1, children: [
2381
+ /* @__PURE__ */ jsx5(QueryHeader, { label: "Query", query: active.query }),
2382
+ /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginTop: 1, children: [
2383
+ /* @__PURE__ */ jsx5(Text5, { color: theme.accent, bold: true, children: "Explanation:" }),
2384
+ /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: active.error ? /* @__PURE__ */ jsx5(ErrorBox, { message: active.error }) : /* @__PURE__ */ jsxs5(Text5, { color: PLACEHOLDER2, children: [
2385
+ active.text,
2386
+ /* @__PURE__ */ jsx5(Text5, { color: PLACEHOLDER2, children: "\u258B" })
2387
+ ] }) })
2388
+ ] })
2498
2389
  ] }),
2499
- isConnected ? /* @__PURE__ */ jsx7(
2390
+ isConnected ? /* @__PURE__ */ jsx5(
2500
2391
  QueryInput,
2501
2392
  {
2502
2393
  onSubmit: handleSubmit,
@@ -2508,16 +2399,16 @@ function App({ connectionState, aiUrl: aiUrl2, aiModel: aiModel2, aiKey: aiKey2,
2508
2399
  aliases,
2509
2400
  schema: schema ?? void 0
2510
2401
  }
2511
- ) : /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "Not connected. Press Ctrl+C to exit." }),
2512
- (vimEnabled || inputIsShell) && /* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx7(Text7, { bold: true, color: inputIsShell ? theme.shellMode : vimMode === "NORMAL" ? theme.normalMode : theme.insertMode, children: isRawModeSupported ? inputIsShell ? "[SHELL]" : `[${vimMode}]` : "" }) })
2402
+ ) : /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "Not connected. Press Ctrl+C to exit." }),
2403
+ (vimEnabled || inputIsShell) && /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text5, { bold: true, color: inputIsShell ? theme.shellMode : vimMode === "NORMAL" ? theme.normalMode : theme.insertMode, children: isRawModeSupported ? inputIsShell ? "[SHELL]" : `[${vimMode}]` : "" }) })
2513
2404
  ] })
2514
2405
  ] });
2515
2406
  }
2516
2407
 
2517
2408
  // src/ui/components/ConnectionWizard.tsx
2518
- import { useState as useState4 } from "react";
2519
- import { Box as Box8, Text as Text8, useInput as useInput3, useStdin as useStdin3 } from "ink";
2520
- import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
2409
+ import { useState as useState5 } from "react";
2410
+ import { Box as Box6, Text as Text6, useInput as useInput3, useStdin as useStdin3 } from "ink";
2411
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
2521
2412
  var DRIVERS = ["postgresql", "mysql", "sqlite"];
2522
2413
  var LABEL_WIDTH = 10;
2523
2414
  function fieldLabels(driver) {
@@ -2526,8 +2417,8 @@ function fieldLabels(driver) {
2526
2417
  }
2527
2418
  function ConnectionWizard({ onConnect, initialError }) {
2528
2419
  const { isRawModeSupported } = useStdin3();
2529
- const [focus, setFocus] = useState4(0);
2530
- const [fields, setFields] = useState4({
2420
+ const [focus, setFocus] = useState5(0);
2421
+ const [fields, setFields] = useState5({
2531
2422
  driver: "postgresql",
2532
2423
  host: "localhost",
2533
2424
  port: "5432",
@@ -2535,9 +2426,9 @@ function ConnectionWizard({ onConnect, initialError }) {
2535
2426
  user: "",
2536
2427
  password: ""
2537
2428
  });
2538
- const [connecting, setConnecting] = useState4(false);
2539
- const [error, setError] = useState4(initialError ?? null);
2540
- const [keychainHint, setKeychainHint] = useState4(false);
2429
+ const [connecting, setConnecting] = useState5(false);
2430
+ const [error, setError] = useState5(initialError ?? null);
2431
+ const [keychainHint, setKeychainHint] = useState5(false);
2541
2432
  const labels = fieldLabels(fields.driver);
2542
2433
  const fieldCount = labels.length;
2543
2434
  function getTextValue(idx) {
@@ -2672,36 +2563,36 @@ function ConnectionWizard({ onConnect, initialError }) {
2672
2563
  { isActive: isRawModeSupported }
2673
2564
  );
2674
2565
  const isPassword = (idx) => fields.driver !== "sqlite" && idx === 5;
2675
- return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", paddingX: 2, paddingTop: 2, paddingBottom: 1, children: [
2676
- /* @__PURE__ */ jsx8(Box8, { marginBottom: 1, children: /* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accent, children: "Connect to a database" }) }),
2566
+ return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", paddingX: 2, paddingTop: 2, paddingBottom: 1, children: [
2567
+ /* @__PURE__ */ jsx6(Box6, { marginBottom: 1, children: /* @__PURE__ */ jsx6(Text6, { bold: true, color: theme.accent, children: "Connect to a database" }) }),
2677
2568
  labels.map((label, idx) => {
2678
2569
  const isFocused = focus === idx;
2679
2570
  const isDriver = idx === 0;
2680
- return /* @__PURE__ */ jsxs8(Box8, { children: [
2681
- /* @__PURE__ */ jsx8(Text8, { color: isFocused ? theme.accent : void 0, bold: isFocused, children: label.padEnd(LABEL_WIDTH) }),
2682
- isDriver ? /* @__PURE__ */ jsx8(Box8, { children: DRIVERS.map((d, i) => /* @__PURE__ */ jsxs8(Box8, { children: [
2683
- i > 0 && /* @__PURE__ */ jsx8(Text8, { children: " " }),
2684
- /* @__PURE__ */ jsxs8(Text8, { color: fields.driver === d ? theme.accent : void 0, bold: fields.driver === d, children: [
2571
+ return /* @__PURE__ */ jsxs6(Box6, { children: [
2572
+ /* @__PURE__ */ jsx6(Text6, { color: isFocused ? theme.accent : void 0, bold: isFocused, children: label.padEnd(LABEL_WIDTH) }),
2573
+ isDriver ? /* @__PURE__ */ jsx6(Box6, { children: DRIVERS.map((d, i) => /* @__PURE__ */ jsxs6(Box6, { children: [
2574
+ i > 0 && /* @__PURE__ */ jsx6(Text6, { children: " " }),
2575
+ /* @__PURE__ */ jsxs6(Text6, { color: fields.driver === d ? theme.accent : void 0, bold: fields.driver === d, children: [
2685
2576
  fields.driver === d ? "\u25CF " : "\u25CB ",
2686
2577
  d.charAt(0).toUpperCase() + d.slice(1)
2687
2578
  ] })
2688
- ] }, d)) }) : /* @__PURE__ */ jsxs8(Box8, { children: [
2689
- /* @__PURE__ */ jsx8(Text8, { children: isPassword(idx) ? "\u2022".repeat(getTextValue(idx).length) : getTextValue(idx) }),
2690
- isFocused && /* @__PURE__ */ jsx8(Text8, { color: theme.accent, bold: true, children: "\u258C" }),
2691
- isFocused && isPassword(idx) && keychainHint && /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: " (from keychain)" })
2579
+ ] }, d)) }) : /* @__PURE__ */ jsxs6(Box6, { children: [
2580
+ /* @__PURE__ */ jsx6(Text6, { children: isPassword(idx) ? "\u2022".repeat(getTextValue(idx).length) : getTextValue(idx) }),
2581
+ isFocused && /* @__PURE__ */ jsx6(Text6, { color: theme.accent, bold: true, children: "\u258C" }),
2582
+ isFocused && isPassword(idx) && keychainHint && /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: " (from keychain)" })
2692
2583
  ] })
2693
2584
  ] }, label);
2694
2585
  }),
2695
- /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: connecting ? /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "Connecting\u2026" }) : error ? /* @__PURE__ */ jsxs8(Text8, { color: theme.error, children: [
2586
+ /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: connecting ? /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "Connecting\u2026" }) : error ? /* @__PURE__ */ jsxs6(Text6, { color: theme.error, children: [
2696
2587
  "\u2717 ",
2697
2588
  error
2698
2589
  ] }) : null }),
2699
- /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "Tab \xB7 \u2191\u2193 navigate Enter connect \u2190\u2192 cycle driver" }) })
2590
+ /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "Tab \xB7 \u2191\u2193 navigate Enter connect \u2190\u2192 cycle driver" }) })
2700
2591
  ] });
2701
2592
  }
2702
2593
 
2703
2594
  // src/index.tsx
2704
- import { jsx as jsx9 } from "react/jsx-runtime";
2595
+ import { jsx as jsx7 } from "react/jsx-runtime";
2705
2596
  var { values } = parseArgs({
2706
2597
  args: process.argv.slice(2).filter((a) => a !== "--"),
2707
2598
  options: {
@@ -2717,8 +2608,8 @@ var aiUrl = values["ai-url"];
2717
2608
  var aiModel = values["ai-model"];
2718
2609
  var aiKey = values["api-key"] || process.env.Q_CLI_API_KEY || "";
2719
2610
  function Root({ initialDsn: initialDsn2, aiUrl: aiUrl2, aiModel: aiModel2, aiKey: aiKey2 }) {
2720
- const [connectionState, setConnectionState] = useState5(null);
2721
- const [dsnError, setDsnError] = useState5(null);
2611
+ const [connectionState, setConnectionState] = useState6(null);
2612
+ const [dsnError, setDsnError] = useState6(null);
2722
2613
  useEffect5(() => {
2723
2614
  if (initialDsn2) {
2724
2615
  connectDsn(initialDsn2).then((state) => {
@@ -2731,10 +2622,10 @@ function Root({ initialDsn: initialDsn2, aiUrl: aiUrl2, aiModel: aiModel2, aiKey
2731
2622
  }
2732
2623
  }, []);
2733
2624
  if (initialDsn2 && !connectionState && !dsnError) {
2734
- return /* @__PURE__ */ jsx9(Box9, { paddingX: 2, paddingTop: 1, children: /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "Connecting\u2026" }) });
2625
+ return /* @__PURE__ */ jsx7(Box7, { paddingX: 2, paddingTop: 1, children: /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "Connecting\u2026" }) });
2735
2626
  }
2736
2627
  if (!connectionState) {
2737
- return /* @__PURE__ */ jsx9(ConnectionWizard, { onConnect: setConnectionState, initialError: dsnError ?? void 0 });
2628
+ return /* @__PURE__ */ jsx7(ConnectionWizard, { onConnect: setConnectionState, initialError: dsnError ?? void 0 });
2738
2629
  }
2739
2630
  async function handleChangeDatabase(database) {
2740
2631
  if (connectionState.status !== "connected") return;
@@ -2744,7 +2635,7 @@ function Root({ initialDsn: initialDsn2, aiUrl: aiUrl2, aiModel: aiModel2, aiKey
2744
2635
  const next = await connectParams({ ...current.params, database });
2745
2636
  setConnectionState(next);
2746
2637
  }
2747
- return /* @__PURE__ */ jsx9(
2638
+ return /* @__PURE__ */ jsx7(
2748
2639
  App,
2749
2640
  {
2750
2641
  connectionState,
@@ -2758,6 +2649,6 @@ function Root({ initialDsn: initialDsn2, aiUrl: aiUrl2, aiModel: aiModel2, aiKey
2758
2649
  );
2759
2650
  }
2760
2651
  render(
2761
- /* @__PURE__ */ jsx9(Root, { initialDsn, aiUrl, aiModel, aiKey }),
2652
+ /* @__PURE__ */ jsx7(Root, { initialDsn, aiUrl, aiModel, aiKey }),
2762
2653
  { exitOnCtrlC: true }
2763
2654
  );