@deaquinodev/querky 0.4.3 → 0.4.4

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 +302 -55
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@
3
3
  // src/index.tsx
4
4
  import { useState as useState5, useEffect as useEffect5 } from "react";
5
5
  import { parseArgs } from "util";
6
- import { render, Box as Box8, Text as Text8 } from "ink";
6
+ import { render, Box as Box9, Text as Text9 } from "ink";
7
7
 
8
8
  // src/db/client.ts
9
9
  import { Client } from "pg";
@@ -196,7 +196,7 @@ import { useState as useState3, useEffect as useEffect4, useRef as useRef2 } fro
196
196
  import { writeFileSync as writeFileSync5 } from "fs";
197
197
  import { homedir as homedir4 } from "os";
198
198
  import { join as join6 } from "path";
199
- import { Box as Box6, Text as Text6, Static, useApp, useInput as useInput2, useStdin as useStdin2 } from "ink";
199
+ import { Box as Box7, Text as Text7, Static, useApp, useInput as useInput2, useStdin as useStdin2 } from "ink";
200
200
 
201
201
  // src/db/query.ts
202
202
  async function runQuery(client, sql) {
@@ -678,6 +678,16 @@ var COMMANDS = {
678
678
  return removed ? { ok: true, message: `Removed /${name}` } : { ok: false, message: `No alias named /${name}` };
679
679
  }
680
680
  },
681
+ "erd": {
682
+ description: "Visualize tables and relationships as a schema diagram",
683
+ category: "Schema",
684
+ usage: "/erd",
685
+ detail: "Fetches all tables, columns, and foreign key relationships from the connected database and renders a color-coded schema diagram. Each table gets a unique color; FK references use the color of the table they point to.",
686
+ run: (ctx) => {
687
+ ctx.onErd();
688
+ return { ok: true, message: "" };
689
+ }
690
+ },
681
691
  "help": {
682
692
  description: "Show help for all commands or a specific command",
683
693
  category: "Session",
@@ -745,6 +755,83 @@ function runCommand(input, ctx) {
745
755
  return { ok: false, message: `Unknown command: ${prefix}${rawName}` };
746
756
  }
747
757
 
758
+ // src/db/erd.ts
759
+ function erdSql(driver) {
760
+ if (driver === "postgresql") {
761
+ return `
762
+ SELECT c.table_name, c.column_name, c.data_type,
763
+ (pk.column_name IS NOT NULL) AS is_pk,
764
+ fk.foreign_table AS fk_table
765
+ FROM information_schema.columns c
766
+ LEFT JOIN (
767
+ SELECT kcu.table_name, kcu.column_name
768
+ FROM information_schema.table_constraints tc
769
+ JOIN information_schema.key_column_usage kcu
770
+ ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema
771
+ WHERE tc.constraint_type = 'PRIMARY KEY' AND tc.table_schema = 'public'
772
+ ) pk ON pk.table_name = c.table_name AND pk.column_name = c.column_name
773
+ LEFT JOIN (
774
+ SELECT kcu.table_name, kcu.column_name, ccu.table_name AS foreign_table
775
+ FROM information_schema.table_constraints tc
776
+ JOIN information_schema.key_column_usage kcu
777
+ ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema
778
+ JOIN information_schema.constraint_column_usage ccu
779
+ ON tc.constraint_name = ccu.constraint_name AND tc.table_schema = ccu.table_schema
780
+ WHERE tc.constraint_type = 'FOREIGN KEY' AND tc.table_schema = 'public'
781
+ ) fk ON fk.table_name = c.table_name AND fk.column_name = c.column_name
782
+ WHERE c.table_schema = 'public'
783
+ ORDER BY c.table_name, c.ordinal_position
784
+ `;
785
+ }
786
+ if (driver === "mysql") {
787
+ return `
788
+ SELECT c.TABLE_NAME AS table_name, c.COLUMN_NAME AS column_name,
789
+ c.DATA_TYPE AS data_type,
790
+ (c.COLUMN_KEY = 'PRI') AS is_pk,
791
+ kcu.REFERENCED_TABLE_NAME AS fk_table
792
+ FROM information_schema.COLUMNS c
793
+ LEFT JOIN information_schema.KEY_COLUMN_USAGE kcu
794
+ ON kcu.TABLE_SCHEMA = c.TABLE_SCHEMA
795
+ AND kcu.TABLE_NAME = c.TABLE_NAME
796
+ AND kcu.COLUMN_NAME = c.COLUMN_NAME
797
+ AND kcu.REFERENCED_TABLE_NAME IS NOT NULL
798
+ WHERE c.TABLE_SCHEMA = DATABASE()
799
+ ORDER BY c.TABLE_NAME, c.ORDINAL_POSITION
800
+ `;
801
+ }
802
+ return `
803
+ SELECT m.name AS table_name, p.name AS column_name,
804
+ p.type AS data_type,
805
+ (p.pk > 0) AS is_pk,
806
+ f."table" AS fk_table
807
+ FROM sqlite_master m
808
+ JOIN pragma_table_info(m.name) p
809
+ LEFT JOIN pragma_foreign_key_list(m.name) f ON f."from" = p.name
810
+ WHERE m.type = 'table' AND m.name NOT LIKE 'sqlite_%'
811
+ ORDER BY m.name, p.cid
812
+ `;
813
+ }
814
+ async function fetchErd(client, driver) {
815
+ const result = await client.query(erdSql(driver));
816
+ const tableMap = /* @__PURE__ */ new Map();
817
+ for (const row of result.rows) {
818
+ const tableName = String(row["table_name"] ?? "");
819
+ if (!tableMap.has(tableName)) tableMap.set(tableName, []);
820
+ const cols = tableMap.get(tableName);
821
+ const colName = String(row["column_name"] ?? "");
822
+ if (cols.some((c) => c.name === colName)) continue;
823
+ cols.push({
824
+ name: colName,
825
+ type: String(row["data_type"] ?? ""),
826
+ isPk: Boolean(row["is_pk"]),
827
+ fkTable: row["fk_table"] ? String(row["fk_table"]) : void 0
828
+ });
829
+ }
830
+ return {
831
+ tables: Array.from(tableMap.entries()).map(([name, columns]) => ({ name, columns }))
832
+ };
833
+ }
834
+
748
835
  // src/commands/shell.ts
749
836
  import { exec } from "child_process";
750
837
  import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, unlinkSync } from "fs";
@@ -1894,8 +1981,138 @@ function HelpView({ data }) {
1894
1981
  return null;
1895
1982
  }
1896
1983
 
1897
- // src/ui/components/App.tsx
1984
+ // src/ui/components/ErdView.tsx
1985
+ import { Box as Box6, Text as Text6 } from "ink";
1898
1986
  import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
1987
+ var TABLE_COLORS = [
1988
+ "#f472b6",
1989
+ "#34d399",
1990
+ "#fb923c",
1991
+ "#60a5fa",
1992
+ "#a78bfa",
1993
+ "#f87171",
1994
+ "#fbbf24",
1995
+ "#2dd4bf"
1996
+ ];
1997
+ var BORDER2 = "white";
1998
+ var PAD = 1;
1999
+ var GAP = 2;
2000
+ var FK_PREFIX = "FK \u2192 ";
2001
+ function computeMetrics(table) {
2002
+ const nameW = Math.max(2, ...table.columns.map((c) => c.name.length));
2003
+ const typeW = Math.max(4, ...table.columns.map((c) => c.type.length));
2004
+ const keyW = Math.max(
2005
+ 2,
2006
+ ...table.columns.map((c) => {
2007
+ if (c.isPk) return 2;
2008
+ if (c.fkTable) return FK_PREFIX.length + c.fkTable.length;
2009
+ return 0;
2010
+ })
2011
+ );
2012
+ const totalW = 4 + 3 * PAD * 2 + nameW + typeW + keyW;
2013
+ return { nameW, typeW, keyW, totalW };
2014
+ }
2015
+ function groupIntoRows(metrics, termW) {
2016
+ const rows = [];
2017
+ let row = [];
2018
+ let usedW = 0;
2019
+ for (let i = 0; i < metrics.length; i++) {
2020
+ const w = metrics[i].totalW;
2021
+ if (row.length === 0) {
2022
+ row.push(i);
2023
+ usedW = w;
2024
+ } else if (usedW + GAP + w <= termW) {
2025
+ row.push(i);
2026
+ usedW += GAP + w;
2027
+ } else {
2028
+ rows.push(row);
2029
+ row = [i];
2030
+ usedW = w;
2031
+ }
2032
+ }
2033
+ if (row.length > 0) rows.push(row);
2034
+ return rows;
2035
+ }
2036
+ function TableBox({ table, m, color, colorMap }) {
2037
+ const { nameW, typeW, keyW, totalW } = m;
2038
+ const sp = " ".repeat(PAD);
2039
+ const p = (s, w) => s.slice(0, w).padEnd(w);
2040
+ const top = "\u256D" + "\u2500".repeat(totalW - 2) + "\u256E";
2041
+ const sep = "\u251C" + "\u2500".repeat(nameW + PAD * 2) + "\u252C" + "\u2500".repeat(typeW + PAD * 2) + "\u252C" + "\u2500".repeat(keyW + PAD * 2) + "\u2524";
2042
+ const bot = "\u2570" + "\u2500".repeat(nameW + PAD * 2) + "\u2534" + "\u2500".repeat(typeW + PAD * 2) + "\u2534" + "\u2500".repeat(keyW + PAD * 2) + "\u256F";
2043
+ const headerW = totalW - 4;
2044
+ return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
2045
+ /* @__PURE__ */ jsx6(Text6, { color: BORDER2, children: top }),
2046
+ /* @__PURE__ */ jsxs6(Box6, { children: [
2047
+ /* @__PURE__ */ jsx6(Text6, { color: BORDER2, children: "\u2502" }),
2048
+ /* @__PURE__ */ jsxs6(Text6, { color, bold: true, children: [
2049
+ sp,
2050
+ p(table.name, headerW),
2051
+ sp
2052
+ ] }),
2053
+ /* @__PURE__ */ jsx6(Text6, { color: BORDER2, children: "\u2502" })
2054
+ ] }),
2055
+ /* @__PURE__ */ jsx6(Text6, { color: BORDER2, children: sep }),
2056
+ table.columns.map((col, i) => /* @__PURE__ */ jsxs6(Box6, { children: [
2057
+ /* @__PURE__ */ jsx6(Text6, { color: BORDER2, children: "\u2502" }),
2058
+ /* @__PURE__ */ jsxs6(Text6, { children: [
2059
+ sp,
2060
+ p(col.name, nameW),
2061
+ sp
2062
+ ] }),
2063
+ /* @__PURE__ */ jsx6(Text6, { color: BORDER2, children: "\u2502" }),
2064
+ /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
2065
+ sp,
2066
+ p(col.type, typeW),
2067
+ sp
2068
+ ] }),
2069
+ /* @__PURE__ */ jsx6(Text6, { color: BORDER2, children: "\u2502" }),
2070
+ col.isPk ? /* @__PURE__ */ jsxs6(Text6, { bold: true, children: [
2071
+ sp,
2072
+ p("PK", keyW),
2073
+ sp
2074
+ ] }) : col.fkTable ? /* @__PURE__ */ jsxs6(Fragment3, { children: [
2075
+ /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
2076
+ sp,
2077
+ FK_PREFIX
2078
+ ] }),
2079
+ /* @__PURE__ */ jsxs6(Text6, { color: colorMap.get(col.fkTable) ?? BORDER2, children: [
2080
+ p(col.fkTable, keyW - FK_PREFIX.length),
2081
+ sp
2082
+ ] })
2083
+ ] }) : /* @__PURE__ */ jsxs6(Text6, { children: [
2084
+ sp,
2085
+ " ".repeat(keyW),
2086
+ sp
2087
+ ] }),
2088
+ /* @__PURE__ */ jsx6(Text6, { color: BORDER2, children: "\u2502" })
2089
+ ] }, i)),
2090
+ /* @__PURE__ */ jsx6(Text6, { color: BORDER2, children: bot })
2091
+ ] });
2092
+ }
2093
+ function ErdView({ data }) {
2094
+ if (data.tables.length === 0) {
2095
+ return /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "No tables found in the current schema." });
2096
+ }
2097
+ const termW = process.stdout.columns ?? 80;
2098
+ const metrics = data.tables.map(computeMetrics);
2099
+ const colorMap = new Map(
2100
+ data.tables.map((t, i) => [t.name, TABLE_COLORS[i % TABLE_COLORS.length]])
2101
+ );
2102
+ const rows = groupIntoRows(metrics, termW);
2103
+ 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(
2104
+ TableBox,
2105
+ {
2106
+ table: data.tables[ti],
2107
+ m: metrics[ti],
2108
+ color: colorMap.get(data.tables[ti].name) ?? BORDER2,
2109
+ colorMap
2110
+ }
2111
+ ) }, ti)) }, ri)) });
2112
+ }
2113
+
2114
+ // src/ui/components/App.tsx
2115
+ import { Fragment as Fragment4, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1899
2116
  var PAGE_SIZE = 50;
1900
2117
  var PLACEHOLDER2 = "#a5b4fc";
1901
2118
  function activePageSize() {
@@ -1909,26 +2126,27 @@ function limitLines(s, n) {
1909
2126
  }
1910
2127
  function EntryView({ entry }) {
1911
2128
  const showAi = entry.aiResponse !== "" || entry.aiError !== null;
2129
+ const showErd = entry.erdData !== null;
1912
2130
  const isShell = entry.query.startsWith("!");
1913
2131
  const isCommand = !isShell && (entry.query.startsWith("/") || entry.query.startsWith("\\"));
1914
2132
  const label = isShell ? "Shell:" : isCommand ? "Command:" : "Query:";
1915
- return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", marginBottom: 1, paddingX: 1, children: [
1916
- /* @__PURE__ */ jsxs6(Box6, { borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
1917
- /* @__PURE__ */ jsxs6(Text6, { color: theme.accent, bold: true, children: [
2133
+ return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", marginBottom: 1, paddingX: 1, children: [
2134
+ /* @__PURE__ */ jsxs7(Box7, { borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
2135
+ /* @__PURE__ */ jsxs7(Text7, { color: theme.accent, bold: true, children: [
1918
2136
  label,
1919
2137
  " "
1920
2138
  ] }),
1921
- /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: entry.query })
2139
+ /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: entry.query })
1922
2140
  ] }),
1923
- /* @__PURE__ */ jsx6(Box6, { marginTop: 1, flexDirection: "column", children: isShell ? entry.shellOutput !== null && /* @__PURE__ */ jsx6(Text6, { children: entry.shellOutput || "(no output)" }) : /* @__PURE__ */ jsxs6(Fragment3, { children: [
1924
- entry.commandMessage && (entry.commandMessage.helpData ? /* @__PURE__ */ jsx6(HelpView, { data: entry.commandMessage.helpData }) : entry.commandMessage.ok ? /* @__PURE__ */ jsxs6(Text6, { color: theme.accent, children: [
2141
+ /* @__PURE__ */ jsx7(Box7, { marginTop: 1, flexDirection: "column", children: isShell ? entry.shellOutput !== null && /* @__PURE__ */ jsx7(Text7, { children: entry.shellOutput || "(no output)" }) : /* @__PURE__ */ jsxs7(Fragment4, { children: [
2142
+ entry.commandMessage && (entry.commandMessage.helpData ? /* @__PURE__ */ jsx7(HelpView, { data: entry.commandMessage.helpData }) : entry.commandMessage.ok ? /* @__PURE__ */ jsxs7(Text7, { color: theme.accent, children: [
1925
2143
  "\u2713 ",
1926
2144
  entry.commandMessage.text
1927
- ] }) : /* @__PURE__ */ jsx6(ErrorBox, { message: entry.commandMessage.text })),
1928
- showAi ? /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
1929
- /* @__PURE__ */ jsx6(Text6, { color: theme.accent, bold: true, children: "Explanation:" }),
1930
- /* @__PURE__ */ jsx6(Box6, { flexDirection: "column", marginTop: 1, children: entry.aiError ? /* @__PURE__ */ jsx6(ErrorBox, { message: entry.aiError }) : /* @__PURE__ */ jsx6(Text6, { color: PLACEHOLDER2, children: entry.aiResponse }) })
1931
- ] }) : !entry.commandMessage && /* @__PURE__ */ jsx6(QueryResult, { state: entry.queryState, elapsed: entry.elapsed, page: entry.page, pageSize: PAGE_SIZE })
2145
+ ] }) : /* @__PURE__ */ jsx7(ErrorBox, { message: entry.commandMessage.text })),
2146
+ showErd ? /* @__PURE__ */ jsx7(ErdView, { data: entry.erdData }) : showAi ? /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
2147
+ /* @__PURE__ */ jsx7(Text7, { color: theme.accent, bold: true, children: "Explanation:" }),
2148
+ /* @__PURE__ */ jsx7(Box7, { flexDirection: "column", marginTop: 1, children: entry.aiError ? /* @__PURE__ */ jsx7(ErrorBox, { message: entry.aiError }) : /* @__PURE__ */ jsx7(Text7, { color: PLACEHOLDER2, children: entry.aiResponse }) })
2149
+ ] }) : !entry.commandMessage && /* @__PURE__ */ jsx7(QueryResult, { state: entry.queryState, elapsed: entry.elapsed, page: entry.page, pageSize: PAGE_SIZE })
1932
2150
  ] }) })
1933
2151
  ] });
1934
2152
  }
@@ -1952,6 +2170,8 @@ function App({ connectionState, aiUrl: aiUrl2, aiModel: aiModel2, aiKey: aiKey2,
1952
2170
  const [aiError, setAiError] = useState3(null);
1953
2171
  const [shellOutput, setShellOutput] = useState3(null);
1954
2172
  const [isShellRunning, setIsShellRunning] = useState3(false);
2173
+ const [erdData, setErdData] = useState3(null);
2174
+ const [isErdLoading, setIsErdLoading] = useState3(false);
1955
2175
  const [completedEntries, setCompletedEntries] = useState3([]);
1956
2176
  const entryIdRef = useRef2(0);
1957
2177
  const aliasScope = connectionState.status === "connected" ? makeScope(connectionState.driver, connectionState.user, connectionState.host, connectionState.database) : "";
@@ -2017,8 +2237,29 @@ function App({ connectionState, aiUrl: aiUrl2, aiModel: aiModel2, aiKey: aiKey2,
2017
2237
  setIsStreaming(false);
2018
2238
  }
2019
2239
  }
2240
+ async function handleErd() {
2241
+ if (connectionState.status !== "connected") {
2242
+ setCommandMessage({ ok: false, text: "Not connected to a database." });
2243
+ return;
2244
+ }
2245
+ setErdData(null);
2246
+ setIsErdLoading(true);
2247
+ setCommandMessage(null);
2248
+ setQueryState({ status: "idle" });
2249
+ setAiResponse("");
2250
+ setAiError(null);
2251
+ try {
2252
+ const data = await fetchErd(connectionState.client, connectionState.driver);
2253
+ setErdData(data);
2254
+ } catch (err) {
2255
+ setCommandMessage({ ok: false, text: err instanceof Error ? err.message : String(err) });
2256
+ } finally {
2257
+ setIsErdLoading(false);
2258
+ }
2259
+ }
2020
2260
  async function handleQuery(sql) {
2021
2261
  if (connectionState.status !== "connected") return;
2262
+ setErdData(null);
2022
2263
  setAiResponse("");
2023
2264
  setAiError(null);
2024
2265
  setCommandMessage(null);
@@ -2076,11 +2317,13 @@ function App({ connectionState, aiUrl: aiUrl2, aiModel: aiModel2, aiKey: aiKey2,
2076
2317
  page,
2077
2318
  aiResponse,
2078
2319
  aiError,
2079
- shellOutput
2320
+ shellOutput,
2321
+ erdData
2080
2322
  }
2081
2323
  ]);
2082
2324
  }
2083
2325
  async function handleShell(cmd) {
2326
+ setErdData(null);
2084
2327
  setShellOutput(null);
2085
2328
  setIsShellRunning(true);
2086
2329
  setCommandMessage(null);
@@ -2126,6 +2369,9 @@ function App({ connectionState, aiUrl: aiUrl2, aiModel: aiModel2, aiKey: aiKey2,
2126
2369
  onChangeDatabase?.(db);
2127
2370
  },
2128
2371
  onExport: handleExport,
2372
+ onErd: () => {
2373
+ void handleErd();
2374
+ },
2129
2375
  onClear: () => {
2130
2376
  process.stdout.write("\x1B[2J\x1B[3J\x1B[H");
2131
2377
  setCompletedEntries([]);
@@ -2144,6 +2390,7 @@ function App({ connectionState, aiUrl: aiUrl2, aiModel: aiModel2, aiKey: aiKey2,
2144
2390
  });
2145
2391
  if (result.cleared) return;
2146
2392
  setLastQuery(sql);
2393
+ setErdData(null);
2147
2394
  setAiResponse("");
2148
2395
  setAiError(null);
2149
2396
  setElapsed(null);
@@ -2156,39 +2403,39 @@ function App({ connectionState, aiUrl: aiUrl2, aiModel: aiModel2, aiKey: aiKey2,
2156
2403
  }
2157
2404
  void handleQuery(sql);
2158
2405
  }
2159
- const isLoading = queryState.status === "running" || isStreaming || isShellRunning;
2406
+ const isLoading = queryState.status === "running" || isStreaming || isShellRunning || isErdLoading;
2160
2407
  const isConnected = connectionState.status === "connected";
2161
2408
  const showAi = aiResponse !== "" || isStreaming || aiError !== null;
2162
2409
  const isShellEntry = lastQuery.startsWith("!");
2163
2410
  const isCommand = !isShellEntry && (lastQuery.startsWith("/") || lastQuery.startsWith("\\"));
2164
2411
  const activeLabel = isShellEntry ? "Shell:" : isCommand ? "Command:" : "Query:";
2165
- return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
2166
- /* @__PURE__ */ jsx6(Static, { items: completedEntries, children: (entry) => /* @__PURE__ */ jsx6(EntryView, { entry }, entry.id) }),
2167
- /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", paddingX: 1, children: [
2168
- lastQuery === "" && /* @__PURE__ */ jsx6(Banner, { connectionState }),
2169
- lastQuery !== "" && /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", marginBottom: 2, children: [
2170
- /* @__PURE__ */ jsxs6(Box6, { borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
2171
- /* @__PURE__ */ jsxs6(Text6, { color: theme.accent, bold: true, children: [
2412
+ return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
2413
+ /* @__PURE__ */ jsx7(Static, { items: completedEntries, children: (entry) => /* @__PURE__ */ jsx7(EntryView, { entry }, entry.id) }),
2414
+ /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", paddingX: 1, children: [
2415
+ lastQuery === "" && /* @__PURE__ */ jsx7(Banner, { connectionState }),
2416
+ lastQuery !== "" && /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", marginBottom: 2, children: [
2417
+ /* @__PURE__ */ jsxs7(Box7, { borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
2418
+ /* @__PURE__ */ jsxs7(Text7, { color: theme.accent, bold: true, children: [
2172
2419
  activeLabel,
2173
2420
  " "
2174
2421
  ] }),
2175
- /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: lastQuery })
2422
+ /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: lastQuery })
2176
2423
  ] }),
2177
- /* @__PURE__ */ jsx6(Box6, { marginTop: 1, flexDirection: "column", children: isShellEntry ? isShellRunning ? /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "running\u2026" }) : /* @__PURE__ */ jsx6(Text6, { children: limitLines(shellOutput ?? "", activePageSize()) }) : /* @__PURE__ */ jsxs6(Fragment3, { children: [
2178
- commandMessage && (commandMessage.helpData ? /* @__PURE__ */ jsx6(HelpView, { data: commandMessage.helpData }) : commandMessage.ok ? /* @__PURE__ */ jsxs6(Text6, { color: theme.accent, children: [
2424
+ /* @__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: [
2425
+ commandMessage && (commandMessage.helpData ? /* @__PURE__ */ jsx7(HelpView, { data: commandMessage.helpData }) : commandMessage.ok ? /* @__PURE__ */ jsxs7(Text7, { color: theme.accent, children: [
2179
2426
  "\u2713 ",
2180
2427
  commandMessage.text
2181
- ] }) : /* @__PURE__ */ jsx6(ErrorBox, { message: commandMessage.text })),
2182
- showAi ? /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
2183
- /* @__PURE__ */ jsx6(Text6, { color: theme.accent, bold: true, children: "Explanation:" }),
2184
- /* @__PURE__ */ jsx6(Box6, { flexDirection: "column", marginTop: 1, children: aiError ? /* @__PURE__ */ jsx6(ErrorBox, { message: aiError }) : /* @__PURE__ */ jsxs6(Text6, { color: PLACEHOLDER2, children: [
2428
+ ] }) : /* @__PURE__ */ jsx7(ErrorBox, { message: commandMessage.text })),
2429
+ isErdLoading ? /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "Fetching schema\u2026" }) : erdData ? /* @__PURE__ */ jsx7(ErdView, { data: erdData }) : showAi ? /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
2430
+ /* @__PURE__ */ jsx7(Text7, { color: theme.accent, bold: true, children: "Explanation:" }),
2431
+ /* @__PURE__ */ jsx7(Box7, { flexDirection: "column", marginTop: 1, children: aiError ? /* @__PURE__ */ jsx7(ErrorBox, { message: aiError }) : /* @__PURE__ */ jsxs7(Text7, { color: PLACEHOLDER2, children: [
2185
2432
  aiResponse,
2186
- isStreaming && /* @__PURE__ */ jsx6(Text6, { color: PLACEHOLDER2, children: "\u258B" })
2433
+ isStreaming && /* @__PURE__ */ jsx7(Text7, { color: PLACEHOLDER2, children: "\u258B" })
2187
2434
  ] }) })
2188
- ] }) : !commandMessage && /* @__PURE__ */ jsx6(QueryResult, { state: queryState, elapsed, page, pageSize: activePageSize() })
2435
+ ] }) : !commandMessage && /* @__PURE__ */ jsx7(QueryResult, { state: queryState, elapsed, page, pageSize: activePageSize() })
2189
2436
  ] }) })
2190
2437
  ] }),
2191
- isConnected ? /* @__PURE__ */ jsx6(
2438
+ isConnected ? /* @__PURE__ */ jsx7(
2192
2439
  QueryInput,
2193
2440
  {
2194
2441
  onSubmit: handleSubmit,
@@ -2200,16 +2447,16 @@ function App({ connectionState, aiUrl: aiUrl2, aiModel: aiModel2, aiKey: aiKey2,
2200
2447
  aliases,
2201
2448
  schema: schema ?? void 0
2202
2449
  }
2203
- ) : /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "Not connected. Press Ctrl+C to exit." }),
2204
- (vimEnabled || inputIsShell) && /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx6(Text6, { bold: true, color: inputIsShell ? theme.shellMode : vimMode === "NORMAL" ? theme.normalMode : theme.insertMode, children: isRawModeSupported ? inputIsShell ? "[SHELL]" : `[${vimMode}]` : "" }) })
2450
+ ) : /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "Not connected. Press Ctrl+C to exit." }),
2451
+ (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}]` : "" }) })
2205
2452
  ] })
2206
2453
  ] });
2207
2454
  }
2208
2455
 
2209
2456
  // src/ui/components/ConnectionWizard.tsx
2210
2457
  import { useState as useState4 } from "react";
2211
- import { Box as Box7, Text as Text7, useInput as useInput3, useStdin as useStdin3 } from "ink";
2212
- import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
2458
+ import { Box as Box8, Text as Text8, useInput as useInput3, useStdin as useStdin3 } from "ink";
2459
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
2213
2460
  var DRIVERS = ["postgresql", "mysql", "sqlite"];
2214
2461
  var LABEL_WIDTH = 10;
2215
2462
  function fieldLabels(driver) {
@@ -2364,36 +2611,36 @@ function ConnectionWizard({ onConnect, initialError }) {
2364
2611
  { isActive: isRawModeSupported }
2365
2612
  );
2366
2613
  const isPassword = (idx) => fields.driver !== "sqlite" && idx === 5;
2367
- return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", paddingX: 2, paddingTop: 2, paddingBottom: 1, children: [
2368
- /* @__PURE__ */ jsx7(Box7, { marginBottom: 1, children: /* @__PURE__ */ jsx7(Text7, { bold: true, color: theme.accent, children: "Connect to a database" }) }),
2614
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", paddingX: 2, paddingTop: 2, paddingBottom: 1, children: [
2615
+ /* @__PURE__ */ jsx8(Box8, { marginBottom: 1, children: /* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accent, children: "Connect to a database" }) }),
2369
2616
  labels.map((label, idx) => {
2370
2617
  const isFocused = focus === idx;
2371
2618
  const isDriver = idx === 0;
2372
- return /* @__PURE__ */ jsxs7(Box7, { children: [
2373
- /* @__PURE__ */ jsx7(Text7, { color: isFocused ? theme.accent : void 0, bold: isFocused, children: label.padEnd(LABEL_WIDTH) }),
2374
- isDriver ? /* @__PURE__ */ jsx7(Box7, { children: DRIVERS.map((d, i) => /* @__PURE__ */ jsxs7(Box7, { children: [
2375
- i > 0 && /* @__PURE__ */ jsx7(Text7, { children: " " }),
2376
- /* @__PURE__ */ jsxs7(Text7, { color: fields.driver === d ? theme.accent : void 0, bold: fields.driver === d, children: [
2619
+ return /* @__PURE__ */ jsxs8(Box8, { children: [
2620
+ /* @__PURE__ */ jsx8(Text8, { color: isFocused ? theme.accent : void 0, bold: isFocused, children: label.padEnd(LABEL_WIDTH) }),
2621
+ isDriver ? /* @__PURE__ */ jsx8(Box8, { children: DRIVERS.map((d, i) => /* @__PURE__ */ jsxs8(Box8, { children: [
2622
+ i > 0 && /* @__PURE__ */ jsx8(Text8, { children: " " }),
2623
+ /* @__PURE__ */ jsxs8(Text8, { color: fields.driver === d ? theme.accent : void 0, bold: fields.driver === d, children: [
2377
2624
  fields.driver === d ? "\u25CF " : "\u25CB ",
2378
2625
  d.charAt(0).toUpperCase() + d.slice(1)
2379
2626
  ] })
2380
- ] }, d)) }) : /* @__PURE__ */ jsxs7(Box7, { children: [
2381
- /* @__PURE__ */ jsx7(Text7, { children: isPassword(idx) ? "\u2022".repeat(getTextValue(idx).length) : getTextValue(idx) }),
2382
- isFocused && /* @__PURE__ */ jsx7(Text7, { color: theme.accent, bold: true, children: "\u258C" }),
2383
- isFocused && isPassword(idx) && keychainHint && /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: " (from keychain)" })
2627
+ ] }, d)) }) : /* @__PURE__ */ jsxs8(Box8, { children: [
2628
+ /* @__PURE__ */ jsx8(Text8, { children: isPassword(idx) ? "\u2022".repeat(getTextValue(idx).length) : getTextValue(idx) }),
2629
+ isFocused && /* @__PURE__ */ jsx8(Text8, { color: theme.accent, bold: true, children: "\u258C" }),
2630
+ isFocused && isPassword(idx) && keychainHint && /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: " (from keychain)" })
2384
2631
  ] })
2385
2632
  ] }, label);
2386
2633
  }),
2387
- /* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: connecting ? /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "Connecting\u2026" }) : error ? /* @__PURE__ */ jsxs7(Text7, { color: theme.error, children: [
2634
+ /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: connecting ? /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "Connecting\u2026" }) : error ? /* @__PURE__ */ jsxs8(Text8, { color: theme.error, children: [
2388
2635
  "\u2717 ",
2389
2636
  error
2390
2637
  ] }) : null }),
2391
- /* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "Tab \xB7 \u2191\u2193 navigate Enter connect \u2190\u2192 cycle driver" }) })
2638
+ /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "Tab \xB7 \u2191\u2193 navigate Enter connect \u2190\u2192 cycle driver" }) })
2392
2639
  ] });
2393
2640
  }
2394
2641
 
2395
2642
  // src/index.tsx
2396
- import { jsx as jsx8 } from "react/jsx-runtime";
2643
+ import { jsx as jsx9 } from "react/jsx-runtime";
2397
2644
  var { values } = parseArgs({
2398
2645
  args: process.argv.slice(2).filter((a) => a !== "--"),
2399
2646
  options: {
@@ -2423,10 +2670,10 @@ function Root({ initialDsn: initialDsn2, aiUrl: aiUrl2, aiModel: aiModel2, aiKey
2423
2670
  }
2424
2671
  }, []);
2425
2672
  if (initialDsn2 && !connectionState && !dsnError) {
2426
- return /* @__PURE__ */ jsx8(Box8, { paddingX: 2, paddingTop: 1, children: /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "Connecting\u2026" }) });
2673
+ return /* @__PURE__ */ jsx9(Box9, { paddingX: 2, paddingTop: 1, children: /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "Connecting\u2026" }) });
2427
2674
  }
2428
2675
  if (!connectionState) {
2429
- return /* @__PURE__ */ jsx8(ConnectionWizard, { onConnect: setConnectionState, initialError: dsnError ?? void 0 });
2676
+ return /* @__PURE__ */ jsx9(ConnectionWizard, { onConnect: setConnectionState, initialError: dsnError ?? void 0 });
2430
2677
  }
2431
2678
  async function handleChangeDatabase(database) {
2432
2679
  if (connectionState.status !== "connected") return;
@@ -2436,7 +2683,7 @@ function Root({ initialDsn: initialDsn2, aiUrl: aiUrl2, aiModel: aiModel2, aiKey
2436
2683
  const next = await connectParams({ ...current.params, database });
2437
2684
  setConnectionState(next);
2438
2685
  }
2439
- return /* @__PURE__ */ jsx8(
2686
+ return /* @__PURE__ */ jsx9(
2440
2687
  App,
2441
2688
  {
2442
2689
  connectionState,
@@ -2450,6 +2697,6 @@ function Root({ initialDsn: initialDsn2, aiUrl: aiUrl2, aiModel: aiModel2, aiKey
2450
2697
  );
2451
2698
  }
2452
2699
  render(
2453
- /* @__PURE__ */ jsx8(Root, { initialDsn, aiUrl, aiModel, aiKey }),
2700
+ /* @__PURE__ */ jsx9(Root, { initialDsn, aiUrl, aiModel, aiKey }),
2454
2701
  { exitOnCtrlC: true }
2455
2702
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deaquinodev/querky",
3
- "version": "0.4.3",
3
+ "version": "0.4.4",
4
4
  "description": "A quirky terminal SQL client with vim mode, AI features, and schema-aware autocomplete",
5
5
  "main": "dist/index.js",
6
6
  "files": [