@deaquinodev/querky 0.4.1 → 0.4.3

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 +204 -66
  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 Box7, Text as Text7 } from "ink";
6
+ import { render, Box as Box8, Text as Text8 } 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 Box5, Text as Text5, Static, useApp, useInput as useInput2, useStdin as useStdin2 } from "ink";
199
+ import { Box as Box6, Text as Text6, 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) {
@@ -466,6 +466,9 @@ var PSQL_ALIASES = {
466
466
  du: "users",
467
467
  c: "changeDatabase"
468
468
  };
469
+ var PSQL_REVERSE = Object.fromEntries(
470
+ Object.entries(PSQL_ALIASES).filter(([alias, name]) => alias !== name).map(([alias, name]) => [name, `\\${alias}`])
471
+ );
469
472
  function describeBasicSql(driver, table) {
470
473
  const t = table.replace(/'/g, "''");
471
474
  if (driver === "postgresql") {
@@ -488,7 +491,10 @@ function describeFullSql(driver, table) {
488
491
  }
489
492
  var COMMANDS = {
490
493
  "clear": {
491
- description: "Clear the scrollback history",
494
+ description: "Clear the terminal scrollback",
495
+ category: "Session",
496
+ usage: "/clear",
497
+ detail: "Erases the visible screen and scrollback buffer. The next query starts fresh from the top.",
492
498
  run: (ctx) => {
493
499
  ctx.onClear();
494
500
  return { ok: true, message: "", cleared: true };
@@ -496,6 +502,9 @@ var COMMANDS = {
496
502
  },
497
503
  "toggle-vim-mode": {
498
504
  description: "Toggle vim keybindings on/off",
505
+ category: "Session",
506
+ usage: "/toggle-vim-mode",
507
+ detail: "Switches the query input between standard editing and vim keybindings. In NORMAL mode use motions like w, b, 0, $, x, dd. Enter INSERT mode with i, a, A, I, o, O.",
499
508
  run: (ctx) => {
500
509
  const next = !ctx.vimEnabled;
501
510
  ctx.setVimEnabled(next);
@@ -503,7 +512,11 @@ var COMMANDS = {
503
512
  }
504
513
  },
505
514
  "d": {
506
- description: "List tables, or describe a table: \\d [table]",
515
+ description: "List tables, or describe a table",
516
+ category: "Schema",
517
+ usage: "/d [table]",
518
+ detail: "Without an argument, lists all tables in the current database. With a table name, shows columns, types, nullability, and defaults.",
519
+ example: "/d users",
507
520
  run: (ctx) => {
508
521
  if (!ctx.args.trim()) {
509
522
  ctx.onQuery(DB_QUERIES[ctx.driver].tables);
@@ -514,7 +527,11 @@ var COMMANDS = {
514
527
  }
515
528
  },
516
529
  "describe": {
517
- description: "Describe a table with constraints: /describe <table>",
530
+ description: "Describe a table with PK/FK/UQ constraints",
531
+ category: "Schema",
532
+ usage: "/describe <table>",
533
+ detail: "Like /d but includes constraint information \u2014 PK (primary key), FK (foreign key), UQ (unique) \u2014 in an extra key column. SQLite shows PK only.",
534
+ example: "/describe orders",
518
535
  run: (ctx) => {
519
536
  const table = ctx.args.trim();
520
537
  if (!table) return { ok: false, message: "Usage: /describe <table>" };
@@ -524,6 +541,9 @@ var COMMANDS = {
524
541
  },
525
542
  "databases": {
526
543
  description: "List available databases",
544
+ category: "Schema",
545
+ usage: "/databases",
546
+ detail: "Runs a driver-appropriate query to list all databases on the server.",
527
547
  run: (ctx) => {
528
548
  ctx.onQuery(DB_QUERIES[ctx.driver].databases);
529
549
  return { ok: true, message: "" };
@@ -531,6 +551,9 @@ var COMMANDS = {
531
551
  },
532
552
  "tables": {
533
553
  description: "List tables in the current database",
554
+ category: "Schema",
555
+ usage: "/tables",
556
+ detail: "Lists tables in the current database's public schema.",
534
557
  run: (ctx) => {
535
558
  ctx.onQuery(DB_QUERIES[ctx.driver].tables);
536
559
  return { ok: true, message: "" };
@@ -538,13 +561,20 @@ var COMMANDS = {
538
561
  },
539
562
  "users": {
540
563
  description: "List database users",
564
+ category: "Schema",
565
+ usage: "/users",
566
+ detail: "Lists users and their roles. On SQLite, shows a placeholder message since SQLite has no user system.",
541
567
  run: (ctx) => {
542
568
  ctx.onQuery(DB_QUERIES[ctx.driver].users);
543
569
  return { ok: true, message: "" };
544
570
  }
545
571
  },
546
572
  "changeDatabase": {
547
- description: "Switch to a different database: \\c dbname",
573
+ description: "Switch to a different database",
574
+ category: "Session",
575
+ usage: "/changeDatabase <dbname>",
576
+ detail: "Switches the active connection to the specified database. Without an argument, shows the current database.",
577
+ example: "/changeDatabase myapp_prod",
548
578
  run: (ctx) => {
549
579
  if (!ctx.args) return { ok: true, message: `Connected to database: ${ctx.currentDatabase}` };
550
580
  ctx.onChangeDatabase(ctx.args.trim());
@@ -552,7 +582,11 @@ var COMMANDS = {
552
582
  }
553
583
  },
554
584
  "export": {
555
- description: "Export last result to a file: /export csv or /export json",
585
+ description: "Export last result to a file",
586
+ category: "Data",
587
+ usage: "/export csv|json",
588
+ detail: "Writes the last query result to a timestamped file in your home directory. CSV files include a header row; JSON files are an array of row objects.",
589
+ example: "/export csv",
556
590
  run: (ctx) => {
557
591
  const fmt = ctx.args.trim().toLowerCase();
558
592
  if (fmt !== "csv" && fmt !== "json") {
@@ -563,7 +597,11 @@ var COMMANDS = {
563
597
  }
564
598
  },
565
599
  "explain": {
566
- description: "Explain a SQL query using AI: /explain SELECT ...",
600
+ description: "Explain a SQL query using AI",
601
+ category: "AI",
602
+ usage: "/explain <SQL>",
603
+ detail: "Sends the query to your configured AI endpoint (Ollama by default, or any OpenAI-compatible API) and streams a plain-language explanation.",
604
+ example: "/explain SELECT * FROM orders WHERE status = 'pending'",
567
605
  run: (ctx) => {
568
606
  if (!ctx.args) return { ok: false, message: "Usage: /explain <SQL query>" };
569
607
  ctx.onExplain(ctx.args);
@@ -572,6 +610,9 @@ var COMMANDS = {
572
610
  },
573
611
  "explain-previous": {
574
612
  description: "Explain the last executed query using AI",
613
+ category: "AI",
614
+ usage: "/explain-previous",
615
+ detail: "Like /explain but uses the last SQL query you ran. Useful for quickly understanding a query after seeing its results.",
575
616
  run: (ctx) => {
576
617
  if (!ctx.lastSqlQuery) {
577
618
  return { ok: false, message: "No query to explain \u2014 run a SQL query first." };
@@ -581,7 +622,11 @@ var COMMANDS = {
581
622
  }
582
623
  },
583
624
  "save": {
584
- description: "Save last query as an alias: /save <name>",
625
+ description: "Save last query as a named alias",
626
+ category: "Aliases",
627
+ usage: "/save <name>",
628
+ detail: "Saves the last executed SQL query as a named alias scoped to the current database. Run it later with /<name>. Supports positional ($1, $2) and named (:param) substitution.",
629
+ example: "/save active-orders",
585
630
  run: (ctx) => {
586
631
  const name = ctx.args.trim();
587
632
  if (!name) return { ok: false, message: "Usage: /save <name>" };
@@ -592,7 +637,11 @@ var COMMANDS = {
592
637
  }
593
638
  },
594
639
  "alias": {
595
- description: "Define an alias inline: /alias <name> <SQL>",
640
+ description: "Define an alias inline",
641
+ category: "Aliases",
642
+ usage: "/alias <name> <SQL>",
643
+ detail: "Defines a named alias without running a query first. Equivalent to /save but lets you specify the SQL directly.",
644
+ example: "/alias recent SELECT * FROM events ORDER BY created_at DESC LIMIT 20",
596
645
  run: (ctx) => {
597
646
  const [name, ...rest] = ctx.args.trim().split(/\s+/);
598
647
  if (!name || rest.length === 0) return { ok: false, message: "Usage: /alias <name> <SQL>" };
@@ -603,6 +652,9 @@ var COMMANDS = {
603
652
  },
604
653
  "aliases": {
605
654
  description: "List all saved aliases for this database",
655
+ category: "Aliases",
656
+ usage: "/aliases",
657
+ detail: "Prints all saved aliases for the current database connection, with their SQL template.",
606
658
  run: (ctx) => {
607
659
  const entries = Object.entries(ctx.aliases);
608
660
  if (entries.length === 0) return { ok: true, message: "No aliases saved. Use /save <name> or /alias <name> <SQL>." };
@@ -614,13 +666,52 @@ var COMMANDS = {
614
666
  }
615
667
  },
616
668
  "unalias": {
617
- description: "Remove a saved alias: /unalias <name>",
669
+ description: "Remove a saved alias",
670
+ category: "Aliases",
671
+ usage: "/unalias <name>",
672
+ detail: "Removes a previously saved alias. Only affects aliases for the current database connection.",
673
+ example: "/unalias active-orders",
618
674
  run: (ctx) => {
619
675
  const name = ctx.args.trim();
620
676
  if (!name) return { ok: false, message: "Usage: /unalias <name>" };
621
677
  const removed = ctx.onDeleteAlias(name);
622
678
  return removed ? { ok: true, message: `Removed /${name}` } : { ok: false, message: `No alias named /${name}` };
623
679
  }
680
+ },
681
+ "help": {
682
+ description: "Show help for all commands or a specific command",
683
+ category: "Session",
684
+ usage: "/help [command]",
685
+ detail: "Without arguments, lists all commands grouped by category. With a command name, shows detailed usage and an example.",
686
+ example: "/help explain",
687
+ run: (ctx) => {
688
+ const arg = ctx.args.trim();
689
+ if (arg) {
690
+ const name = PSQL_ALIASES[arg] ?? arg;
691
+ const cmd = COMMANDS[name];
692
+ if (!cmd) return { ok: false, message: `Unknown command: /${arg}` };
693
+ const entry = {
694
+ name,
695
+ usage: cmd.usage,
696
+ description: cmd.description,
697
+ psqlAlias: PSQL_REVERSE[name],
698
+ detail: cmd.detail,
699
+ example: cmd.example
700
+ };
701
+ return { ok: true, message: "", helpData: { mode: "detail", entry } };
702
+ }
703
+ const categoryOrder = ["AI", "Schema", "Data", "Aliases", "Session"];
704
+ const groups = categoryOrder.map((cat) => ({
705
+ category: cat,
706
+ entries: Object.entries(COMMANDS).filter(([, cmd]) => cmd.category === cat).map(([name, cmd]) => ({
707
+ name,
708
+ usage: cmd.usage,
709
+ description: cmd.description,
710
+ psqlAlias: PSQL_REVERSE[name]
711
+ }))
712
+ })).filter((g) => g.entries.length > 0);
713
+ return { ok: true, message: "", helpData: { mode: "list", groups } };
714
+ }
624
715
  }
625
716
  };
626
717
  var BUILTIN_COMMAND_LIST = Object.entries(COMMANDS).map(([name, cmd]) => ({
@@ -1617,11 +1708,14 @@ var ERROR_FG = "#ff4444";
1617
1708
  function ErrorBox({ message }) {
1618
1709
  const cols = Math.max(0, (process.stdout.columns ?? 80) - 2);
1619
1710
  const blank = " ".repeat(cols);
1620
- const label = ` \u2717 ${message}`;
1621
- const padded = label.length < cols ? label + " ".repeat(cols - label.length) : label;
1711
+ const lines = message.split("\n");
1622
1712
  return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", marginTop: 1, children: [
1623
1713
  /* @__PURE__ */ jsx3(Text3, { backgroundColor: ERROR_BG, children: blank }),
1624
- /* @__PURE__ */ jsx3(Text3, { backgroundColor: ERROR_BG, color: ERROR_FG, bold: true, children: padded }),
1714
+ lines.map((line, i) => {
1715
+ const content = (i === 0 ? " \u2717 " : " ") + line;
1716
+ const padded = content.length < cols ? content + " ".repeat(cols - content.length) : content;
1717
+ return /* @__PURE__ */ jsx3(Text3, { backgroundColor: ERROR_BG, color: ERROR_FG, bold: true, children: padded }, i);
1718
+ }),
1625
1719
  /* @__PURE__ */ jsx3(Text3, { backgroundColor: ERROR_BG, children: blank })
1626
1720
  ] });
1627
1721
  }
@@ -1757,8 +1851,51 @@ function Banner({ connectionState }) {
1757
1851
  ] });
1758
1852
  }
1759
1853
 
1854
+ // src/ui/components/HelpView.tsx
1855
+ import { Box as Box5, Text as Text5 } from "ink";
1856
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
1857
+ var USAGE_WIDTH = 28;
1858
+ function HelpView({ data }) {
1859
+ if (data.mode === "detail" && data.entry) {
1860
+ const { usage, description, psqlAlias, detail, example } = data.entry;
1861
+ return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginTop: 1, children: [
1862
+ /* @__PURE__ */ jsxs5(Text5, { bold: true, color: "white", children: [
1863
+ usage,
1864
+ psqlAlias ? ` ${psqlAlias}` : ""
1865
+ ] }),
1866
+ /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text5, { children: description }) }),
1867
+ detail && /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: detail }) }),
1868
+ example && /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, children: [
1869
+ /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "Example: " }),
1870
+ /* @__PURE__ */ jsx5(Text5, { color: theme.accent, children: example })
1871
+ ] })
1872
+ ] });
1873
+ }
1874
+ if (data.mode === "list" && data.groups) {
1875
+ return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginTop: 1, children: [
1876
+ data.groups.map((group, gi) => /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginBottom: gi < data.groups.length - 1 ? 1 : 0, children: [
1877
+ /* @__PURE__ */ jsx5(Text5, { bold: true, color: theme.accent, children: group.category }),
1878
+ group.entries.map((entry) => /* @__PURE__ */ jsxs5(Box5, { marginLeft: 2, children: [
1879
+ /* @__PURE__ */ jsx5(Text5, { color: "white", children: entry.usage.padEnd(USAGE_WIDTH) }),
1880
+ /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: entry.description }),
1881
+ entry.psqlAlias && /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
1882
+ " ",
1883
+ entry.psqlAlias
1884
+ ] })
1885
+ ] }, entry.name))
1886
+ ] }, group.category)),
1887
+ /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
1888
+ "/help ",
1889
+ "<command>",
1890
+ " for more details."
1891
+ ] }) })
1892
+ ] });
1893
+ }
1894
+ return null;
1895
+ }
1896
+
1760
1897
  // src/ui/components/App.tsx
1761
- import { Fragment as Fragment3, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
1898
+ import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
1762
1899
  var PAGE_SIZE = 50;
1763
1900
  var PLACEHOLDER2 = "#a5b4fc";
1764
1901
  function activePageSize() {
@@ -1775,23 +1912,23 @@ function EntryView({ entry }) {
1775
1912
  const isShell = entry.query.startsWith("!");
1776
1913
  const isCommand = !isShell && (entry.query.startsWith("/") || entry.query.startsWith("\\"));
1777
1914
  const label = isShell ? "Shell:" : isCommand ? "Command:" : "Query:";
1778
- return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginBottom: 1, paddingX: 1, children: [
1779
- /* @__PURE__ */ jsxs5(Box5, { borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
1780
- /* @__PURE__ */ jsxs5(Text5, { color: theme.accent, bold: true, children: [
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: [
1781
1918
  label,
1782
1919
  " "
1783
1920
  ] }),
1784
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: entry.query })
1921
+ /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: entry.query })
1785
1922
  ] }),
1786
- /* @__PURE__ */ jsx5(Box5, { marginTop: 1, flexDirection: "column", children: isShell ? entry.shellOutput !== null && /* @__PURE__ */ jsx5(Text5, { children: entry.shellOutput || "(no output)" }) : /* @__PURE__ */ jsxs5(Fragment3, { children: [
1787
- entry.commandMessage && (entry.commandMessage.ok ? /* @__PURE__ */ jsxs5(Text5, { color: theme.accent, children: [
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: [
1788
1925
  "\u2713 ",
1789
1926
  entry.commandMessage.text
1790
- ] }) : /* @__PURE__ */ jsx5(ErrorBox, { message: entry.commandMessage.text })),
1791
- showAi ? /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
1792
- /* @__PURE__ */ jsx5(Text5, { color: theme.accent, bold: true, children: "Explanation:" }),
1793
- /* @__PURE__ */ jsx5(Box5, { flexDirection: "column", marginTop: 1, children: entry.aiError ? /* @__PURE__ */ jsx5(ErrorBox, { message: entry.aiError }) : /* @__PURE__ */ jsx5(Text5, { color: PLACEHOLDER2, children: entry.aiResponse }) })
1794
- ] }) : !entry.commandMessage && /* @__PURE__ */ jsx5(QueryResult, { state: entry.queryState, elapsed: entry.elapsed, page: entry.page, pageSize: PAGE_SIZE })
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 })
1795
1932
  ] }) })
1796
1933
  ] });
1797
1934
  }
@@ -2012,7 +2149,8 @@ function App({ connectionState, aiUrl: aiUrl2, aiModel: aiModel2, aiKey: aiKey2,
2012
2149
  setElapsed(null);
2013
2150
  setPage(0);
2014
2151
  setQueryState({ status: "idle" });
2015
- if (result.message) setCommandMessage({ ok: result.ok, text: result.message });
2152
+ if (result.helpData) setCommandMessage({ ok: result.ok, text: "", helpData: result.helpData });
2153
+ else if (result.message) setCommandMessage({ ok: result.ok, text: result.message });
2016
2154
  else setCommandMessage(null);
2017
2155
  return;
2018
2156
  }
@@ -2024,33 +2162,33 @@ function App({ connectionState, aiUrl: aiUrl2, aiModel: aiModel2, aiKey: aiKey2,
2024
2162
  const isShellEntry = lastQuery.startsWith("!");
2025
2163
  const isCommand = !isShellEntry && (lastQuery.startsWith("/") || lastQuery.startsWith("\\"));
2026
2164
  const activeLabel = isShellEntry ? "Shell:" : isCommand ? "Command:" : "Query:";
2027
- return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
2028
- /* @__PURE__ */ jsx5(Static, { items: completedEntries, children: (entry) => /* @__PURE__ */ jsx5(EntryView, { entry }, entry.id) }),
2029
- /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", paddingX: 1, children: [
2030
- lastQuery === "" && /* @__PURE__ */ jsx5(Banner, { connectionState }),
2031
- lastQuery !== "" && /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginBottom: 2, children: [
2032
- /* @__PURE__ */ jsxs5(Box5, { borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
2033
- /* @__PURE__ */ jsxs5(Text5, { color: theme.accent, bold: true, children: [
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: [
2034
2172
  activeLabel,
2035
2173
  " "
2036
2174
  ] }),
2037
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: lastQuery })
2175
+ /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: lastQuery })
2038
2176
  ] }),
2039
- /* @__PURE__ */ jsx5(Box5, { marginTop: 1, flexDirection: "column", children: isShellEntry ? isShellRunning ? /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "running\u2026" }) : /* @__PURE__ */ jsx5(Text5, { children: limitLines(shellOutput ?? "", activePageSize()) }) : /* @__PURE__ */ jsxs5(Fragment3, { children: [
2040
- commandMessage && (commandMessage.ok ? /* @__PURE__ */ jsxs5(Text5, { color: theme.accent, children: [
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: [
2041
2179
  "\u2713 ",
2042
2180
  commandMessage.text
2043
- ] }) : /* @__PURE__ */ jsx5(ErrorBox, { message: commandMessage.text })),
2044
- showAi ? /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
2045
- /* @__PURE__ */ jsx5(Text5, { color: theme.accent, bold: true, children: "Explanation:" }),
2046
- /* @__PURE__ */ jsx5(Box5, { flexDirection: "column", marginTop: 1, children: aiError ? /* @__PURE__ */ jsx5(ErrorBox, { message: aiError }) : /* @__PURE__ */ jsxs5(Text5, { color: PLACEHOLDER2, children: [
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: [
2047
2185
  aiResponse,
2048
- isStreaming && /* @__PURE__ */ jsx5(Text5, { color: PLACEHOLDER2, children: "\u258B" })
2186
+ isStreaming && /* @__PURE__ */ jsx6(Text6, { color: PLACEHOLDER2, children: "\u258B" })
2049
2187
  ] }) })
2050
- ] }) : !commandMessage && /* @__PURE__ */ jsx5(QueryResult, { state: queryState, elapsed, page, pageSize: activePageSize() })
2188
+ ] }) : !commandMessage && /* @__PURE__ */ jsx6(QueryResult, { state: queryState, elapsed, page, pageSize: activePageSize() })
2051
2189
  ] }) })
2052
2190
  ] }),
2053
- isConnected ? /* @__PURE__ */ jsx5(
2191
+ isConnected ? /* @__PURE__ */ jsx6(
2054
2192
  QueryInput,
2055
2193
  {
2056
2194
  onSubmit: handleSubmit,
@@ -2062,16 +2200,16 @@ function App({ connectionState, aiUrl: aiUrl2, aiModel: aiModel2, aiKey: aiKey2,
2062
2200
  aliases,
2063
2201
  schema: schema ?? void 0
2064
2202
  }
2065
- ) : /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "Not connected. Press Ctrl+C to exit." }),
2066
- (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}]` : "" }) })
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}]` : "" }) })
2067
2205
  ] })
2068
2206
  ] });
2069
2207
  }
2070
2208
 
2071
2209
  // src/ui/components/ConnectionWizard.tsx
2072
2210
  import { useState as useState4 } from "react";
2073
- import { Box as Box6, Text as Text6, useInput as useInput3, useStdin as useStdin3 } from "ink";
2074
- import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
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";
2075
2213
  var DRIVERS = ["postgresql", "mysql", "sqlite"];
2076
2214
  var LABEL_WIDTH = 10;
2077
2215
  function fieldLabels(driver) {
@@ -2226,36 +2364,36 @@ function ConnectionWizard({ onConnect, initialError }) {
2226
2364
  { isActive: isRawModeSupported }
2227
2365
  );
2228
2366
  const isPassword = (idx) => fields.driver !== "sqlite" && idx === 5;
2229
- return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", paddingX: 2, paddingTop: 2, paddingBottom: 1, children: [
2230
- /* @__PURE__ */ jsx6(Box6, { marginBottom: 1, children: /* @__PURE__ */ jsx6(Text6, { bold: true, color: theme.accent, children: "Connect to a database" }) }),
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" }) }),
2231
2369
  labels.map((label, idx) => {
2232
2370
  const isFocused = focus === idx;
2233
2371
  const isDriver = idx === 0;
2234
- return /* @__PURE__ */ jsxs6(Box6, { children: [
2235
- /* @__PURE__ */ jsx6(Text6, { color: isFocused ? theme.accent : void 0, bold: isFocused, children: label.padEnd(LABEL_WIDTH) }),
2236
- isDriver ? /* @__PURE__ */ jsx6(Box6, { children: DRIVERS.map((d, i) => /* @__PURE__ */ jsxs6(Box6, { children: [
2237
- i > 0 && /* @__PURE__ */ jsx6(Text6, { children: " " }),
2238
- /* @__PURE__ */ jsxs6(Text6, { color: fields.driver === d ? theme.accent : void 0, bold: fields.driver === d, children: [
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: [
2239
2377
  fields.driver === d ? "\u25CF " : "\u25CB ",
2240
2378
  d.charAt(0).toUpperCase() + d.slice(1)
2241
2379
  ] })
2242
- ] }, d)) }) : /* @__PURE__ */ jsxs6(Box6, { children: [
2243
- /* @__PURE__ */ jsx6(Text6, { children: isPassword(idx) ? "\u2022".repeat(getTextValue(idx).length) : getTextValue(idx) }),
2244
- isFocused && /* @__PURE__ */ jsx6(Text6, { color: theme.accent, bold: true, children: "\u258C" }),
2245
- isFocused && isPassword(idx) && keychainHint && /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: " (from keychain)" })
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)" })
2246
2384
  ] })
2247
2385
  ] }, label);
2248
2386
  }),
2249
- /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: connecting ? /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "Connecting\u2026" }) : error ? /* @__PURE__ */ jsxs6(Text6, { color: theme.error, children: [
2387
+ /* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: connecting ? /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "Connecting\u2026" }) : error ? /* @__PURE__ */ jsxs7(Text7, { color: theme.error, children: [
2250
2388
  "\u2717 ",
2251
2389
  error
2252
2390
  ] }) : null }),
2253
- /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "Tab \xB7 \u2191\u2193 navigate Enter connect \u2190\u2192 cycle driver" }) })
2391
+ /* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "Tab \xB7 \u2191\u2193 navigate Enter connect \u2190\u2192 cycle driver" }) })
2254
2392
  ] });
2255
2393
  }
2256
2394
 
2257
2395
  // src/index.tsx
2258
- import { jsx as jsx7 } from "react/jsx-runtime";
2396
+ import { jsx as jsx8 } from "react/jsx-runtime";
2259
2397
  var { values } = parseArgs({
2260
2398
  args: process.argv.slice(2).filter((a) => a !== "--"),
2261
2399
  options: {
@@ -2285,10 +2423,10 @@ function Root({ initialDsn: initialDsn2, aiUrl: aiUrl2, aiModel: aiModel2, aiKey
2285
2423
  }
2286
2424
  }, []);
2287
2425
  if (initialDsn2 && !connectionState && !dsnError) {
2288
- return /* @__PURE__ */ jsx7(Box7, { paddingX: 2, paddingTop: 1, children: /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "Connecting\u2026" }) });
2426
+ return /* @__PURE__ */ jsx8(Box8, { paddingX: 2, paddingTop: 1, children: /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "Connecting\u2026" }) });
2289
2427
  }
2290
2428
  if (!connectionState) {
2291
- return /* @__PURE__ */ jsx7(ConnectionWizard, { onConnect: setConnectionState, initialError: dsnError ?? void 0 });
2429
+ return /* @__PURE__ */ jsx8(ConnectionWizard, { onConnect: setConnectionState, initialError: dsnError ?? void 0 });
2292
2430
  }
2293
2431
  async function handleChangeDatabase(database) {
2294
2432
  if (connectionState.status !== "connected") return;
@@ -2298,7 +2436,7 @@ function Root({ initialDsn: initialDsn2, aiUrl: aiUrl2, aiModel: aiModel2, aiKey
2298
2436
  const next = await connectParams({ ...current.params, database });
2299
2437
  setConnectionState(next);
2300
2438
  }
2301
- return /* @__PURE__ */ jsx7(
2439
+ return /* @__PURE__ */ jsx8(
2302
2440
  App,
2303
2441
  {
2304
2442
  connectionState,
@@ -2312,6 +2450,6 @@ function Root({ initialDsn: initialDsn2, aiUrl: aiUrl2, aiModel: aiModel2, aiKey
2312
2450
  );
2313
2451
  }
2314
2452
  render(
2315
- /* @__PURE__ */ jsx7(Root, { initialDsn, aiUrl, aiModel, aiKey }),
2453
+ /* @__PURE__ */ jsx8(Root, { initialDsn, aiUrl, aiModel, aiKey }),
2316
2454
  { exitOnCtrlC: true }
2317
2455
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deaquinodev/querky",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
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": [