@deaquinodev/querky 0.4.2 → 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 +198 -63
  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]) => ({
@@ -1760,8 +1851,51 @@ function Banner({ connectionState }) {
1760
1851
  ] });
1761
1852
  }
1762
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
+
1763
1897
  // src/ui/components/App.tsx
1764
- 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";
1765
1899
  var PAGE_SIZE = 50;
1766
1900
  var PLACEHOLDER2 = "#a5b4fc";
1767
1901
  function activePageSize() {
@@ -1778,23 +1912,23 @@ function EntryView({ entry }) {
1778
1912
  const isShell = entry.query.startsWith("!");
1779
1913
  const isCommand = !isShell && (entry.query.startsWith("/") || entry.query.startsWith("\\"));
1780
1914
  const label = isShell ? "Shell:" : isCommand ? "Command:" : "Query:";
1781
- return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginBottom: 1, paddingX: 1, children: [
1782
- /* @__PURE__ */ jsxs5(Box5, { borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
1783
- /* @__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: [
1784
1918
  label,
1785
1919
  " "
1786
1920
  ] }),
1787
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: entry.query })
1921
+ /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: entry.query })
1788
1922
  ] }),
1789
- /* @__PURE__ */ jsx5(Box5, { marginTop: 1, flexDirection: "column", children: isShell ? entry.shellOutput !== null && /* @__PURE__ */ jsx5(Text5, { children: entry.shellOutput || "(no output)" }) : /* @__PURE__ */ jsxs5(Fragment3, { children: [
1790
- 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: [
1791
1925
  "\u2713 ",
1792
1926
  entry.commandMessage.text
1793
- ] }) : /* @__PURE__ */ jsx5(ErrorBox, { message: entry.commandMessage.text })),
1794
- showAi ? /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
1795
- /* @__PURE__ */ jsx5(Text5, { color: theme.accent, bold: true, children: "Explanation:" }),
1796
- /* @__PURE__ */ jsx5(Box5, { flexDirection: "column", marginTop: 1, children: entry.aiError ? /* @__PURE__ */ jsx5(ErrorBox, { message: entry.aiError }) : /* @__PURE__ */ jsx5(Text5, { color: PLACEHOLDER2, children: entry.aiResponse }) })
1797
- ] }) : !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 })
1798
1932
  ] }) })
1799
1933
  ] });
1800
1934
  }
@@ -2015,7 +2149,8 @@ function App({ connectionState, aiUrl: aiUrl2, aiModel: aiModel2, aiKey: aiKey2,
2015
2149
  setElapsed(null);
2016
2150
  setPage(0);
2017
2151
  setQueryState({ status: "idle" });
2018
- 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 });
2019
2154
  else setCommandMessage(null);
2020
2155
  return;
2021
2156
  }
@@ -2027,33 +2162,33 @@ function App({ connectionState, aiUrl: aiUrl2, aiModel: aiModel2, aiKey: aiKey2,
2027
2162
  const isShellEntry = lastQuery.startsWith("!");
2028
2163
  const isCommand = !isShellEntry && (lastQuery.startsWith("/") || lastQuery.startsWith("\\"));
2029
2164
  const activeLabel = isShellEntry ? "Shell:" : isCommand ? "Command:" : "Query:";
2030
- return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
2031
- /* @__PURE__ */ jsx5(Static, { items: completedEntries, children: (entry) => /* @__PURE__ */ jsx5(EntryView, { entry }, entry.id) }),
2032
- /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", paddingX: 1, children: [
2033
- lastQuery === "" && /* @__PURE__ */ jsx5(Banner, { connectionState }),
2034
- lastQuery !== "" && /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginBottom: 2, children: [
2035
- /* @__PURE__ */ jsxs5(Box5, { borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
2036
- /* @__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: [
2037
2172
  activeLabel,
2038
2173
  " "
2039
2174
  ] }),
2040
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: lastQuery })
2175
+ /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: lastQuery })
2041
2176
  ] }),
2042
- /* @__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: [
2043
- 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: [
2044
2179
  "\u2713 ",
2045
2180
  commandMessage.text
2046
- ] }) : /* @__PURE__ */ jsx5(ErrorBox, { message: commandMessage.text })),
2047
- showAi ? /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
2048
- /* @__PURE__ */ jsx5(Text5, { color: theme.accent, bold: true, children: "Explanation:" }),
2049
- /* @__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: [
2050
2185
  aiResponse,
2051
- isStreaming && /* @__PURE__ */ jsx5(Text5, { color: PLACEHOLDER2, children: "\u258B" })
2186
+ isStreaming && /* @__PURE__ */ jsx6(Text6, { color: PLACEHOLDER2, children: "\u258B" })
2052
2187
  ] }) })
2053
- ] }) : !commandMessage && /* @__PURE__ */ jsx5(QueryResult, { state: queryState, elapsed, page, pageSize: activePageSize() })
2188
+ ] }) : !commandMessage && /* @__PURE__ */ jsx6(QueryResult, { state: queryState, elapsed, page, pageSize: activePageSize() })
2054
2189
  ] }) })
2055
2190
  ] }),
2056
- isConnected ? /* @__PURE__ */ jsx5(
2191
+ isConnected ? /* @__PURE__ */ jsx6(
2057
2192
  QueryInput,
2058
2193
  {
2059
2194
  onSubmit: handleSubmit,
@@ -2065,16 +2200,16 @@ function App({ connectionState, aiUrl: aiUrl2, aiModel: aiModel2, aiKey: aiKey2,
2065
2200
  aliases,
2066
2201
  schema: schema ?? void 0
2067
2202
  }
2068
- ) : /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "Not connected. Press Ctrl+C to exit." }),
2069
- (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}]` : "" }) })
2070
2205
  ] })
2071
2206
  ] });
2072
2207
  }
2073
2208
 
2074
2209
  // src/ui/components/ConnectionWizard.tsx
2075
2210
  import { useState as useState4 } from "react";
2076
- import { Box as Box6, Text as Text6, useInput as useInput3, useStdin as useStdin3 } from "ink";
2077
- 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";
2078
2213
  var DRIVERS = ["postgresql", "mysql", "sqlite"];
2079
2214
  var LABEL_WIDTH = 10;
2080
2215
  function fieldLabels(driver) {
@@ -2229,36 +2364,36 @@ function ConnectionWizard({ onConnect, initialError }) {
2229
2364
  { isActive: isRawModeSupported }
2230
2365
  );
2231
2366
  const isPassword = (idx) => fields.driver !== "sqlite" && idx === 5;
2232
- return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", paddingX: 2, paddingTop: 2, paddingBottom: 1, children: [
2233
- /* @__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" }) }),
2234
2369
  labels.map((label, idx) => {
2235
2370
  const isFocused = focus === idx;
2236
2371
  const isDriver = idx === 0;
2237
- return /* @__PURE__ */ jsxs6(Box6, { children: [
2238
- /* @__PURE__ */ jsx6(Text6, { color: isFocused ? theme.accent : void 0, bold: isFocused, children: label.padEnd(LABEL_WIDTH) }),
2239
- isDriver ? /* @__PURE__ */ jsx6(Box6, { children: DRIVERS.map((d, i) => /* @__PURE__ */ jsxs6(Box6, { children: [
2240
- i > 0 && /* @__PURE__ */ jsx6(Text6, { children: " " }),
2241
- /* @__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: [
2242
2377
  fields.driver === d ? "\u25CF " : "\u25CB ",
2243
2378
  d.charAt(0).toUpperCase() + d.slice(1)
2244
2379
  ] })
2245
- ] }, d)) }) : /* @__PURE__ */ jsxs6(Box6, { children: [
2246
- /* @__PURE__ */ jsx6(Text6, { children: isPassword(idx) ? "\u2022".repeat(getTextValue(idx).length) : getTextValue(idx) }),
2247
- isFocused && /* @__PURE__ */ jsx6(Text6, { color: theme.accent, bold: true, children: "\u258C" }),
2248
- 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)" })
2249
2384
  ] })
2250
2385
  ] }, label);
2251
2386
  }),
2252
- /* @__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: [
2253
2388
  "\u2717 ",
2254
2389
  error
2255
2390
  ] }) : null }),
2256
- /* @__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" }) })
2257
2392
  ] });
2258
2393
  }
2259
2394
 
2260
2395
  // src/index.tsx
2261
- import { jsx as jsx7 } from "react/jsx-runtime";
2396
+ import { jsx as jsx8 } from "react/jsx-runtime";
2262
2397
  var { values } = parseArgs({
2263
2398
  args: process.argv.slice(2).filter((a) => a !== "--"),
2264
2399
  options: {
@@ -2288,10 +2423,10 @@ function Root({ initialDsn: initialDsn2, aiUrl: aiUrl2, aiModel: aiModel2, aiKey
2288
2423
  }
2289
2424
  }, []);
2290
2425
  if (initialDsn2 && !connectionState && !dsnError) {
2291
- 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" }) });
2292
2427
  }
2293
2428
  if (!connectionState) {
2294
- return /* @__PURE__ */ jsx7(ConnectionWizard, { onConnect: setConnectionState, initialError: dsnError ?? void 0 });
2429
+ return /* @__PURE__ */ jsx8(ConnectionWizard, { onConnect: setConnectionState, initialError: dsnError ?? void 0 });
2295
2430
  }
2296
2431
  async function handleChangeDatabase(database) {
2297
2432
  if (connectionState.status !== "connected") return;
@@ -2301,7 +2436,7 @@ function Root({ initialDsn: initialDsn2, aiUrl: aiUrl2, aiModel: aiModel2, aiKey
2301
2436
  const next = await connectParams({ ...current.params, database });
2302
2437
  setConnectionState(next);
2303
2438
  }
2304
- return /* @__PURE__ */ jsx7(
2439
+ return /* @__PURE__ */ jsx8(
2305
2440
  App,
2306
2441
  {
2307
2442
  connectionState,
@@ -2315,6 +2450,6 @@ function Root({ initialDsn: initialDsn2, aiUrl: aiUrl2, aiModel: aiModel2, aiKey
2315
2450
  );
2316
2451
  }
2317
2452
  render(
2318
- /* @__PURE__ */ jsx7(Root, { initialDsn, aiUrl, aiModel, aiKey }),
2453
+ /* @__PURE__ */ jsx8(Root, { initialDsn, aiUrl, aiModel, aiKey }),
2319
2454
  { exitOnCtrlC: true }
2320
2455
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deaquinodev/querky",
3
- "version": "0.4.2",
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": [