@deaquinodev/querky 0.4.2 → 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 +447 -65
  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 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 Box5, Text as Text5, 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) {
@@ -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,62 @@ 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
+ "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
+ },
691
+ "help": {
692
+ description: "Show help for all commands or a specific command",
693
+ category: "Session",
694
+ usage: "/help [command]",
695
+ detail: "Without arguments, lists all commands grouped by category. With a command name, shows detailed usage and an example.",
696
+ example: "/help explain",
697
+ run: (ctx) => {
698
+ const arg = ctx.args.trim();
699
+ if (arg) {
700
+ const name = PSQL_ALIASES[arg] ?? arg;
701
+ const cmd = COMMANDS[name];
702
+ if (!cmd) return { ok: false, message: `Unknown command: /${arg}` };
703
+ const entry = {
704
+ name,
705
+ usage: cmd.usage,
706
+ description: cmd.description,
707
+ psqlAlias: PSQL_REVERSE[name],
708
+ detail: cmd.detail,
709
+ example: cmd.example
710
+ };
711
+ return { ok: true, message: "", helpData: { mode: "detail", entry } };
712
+ }
713
+ const categoryOrder = ["AI", "Schema", "Data", "Aliases", "Session"];
714
+ const groups = categoryOrder.map((cat) => ({
715
+ category: cat,
716
+ entries: Object.entries(COMMANDS).filter(([, cmd]) => cmd.category === cat).map(([name, cmd]) => ({
717
+ name,
718
+ usage: cmd.usage,
719
+ description: cmd.description,
720
+ psqlAlias: PSQL_REVERSE[name]
721
+ }))
722
+ })).filter((g) => g.entries.length > 0);
723
+ return { ok: true, message: "", helpData: { mode: "list", groups } };
724
+ }
624
725
  }
625
726
  };
626
727
  var BUILTIN_COMMAND_LIST = Object.entries(COMMANDS).map(([name, cmd]) => ({
@@ -654,6 +755,83 @@ function runCommand(input, ctx) {
654
755
  return { ok: false, message: `Unknown command: ${prefix}${rawName}` };
655
756
  }
656
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
+
657
835
  // src/commands/shell.ts
658
836
  import { exec } from "child_process";
659
837
  import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, unlinkSync } from "fs";
@@ -1760,8 +1938,181 @@ function Banner({ connectionState }) {
1760
1938
  ] });
1761
1939
  }
1762
1940
 
1941
+ // src/ui/components/HelpView.tsx
1942
+ import { Box as Box5, Text as Text5 } from "ink";
1943
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
1944
+ var USAGE_WIDTH = 28;
1945
+ function HelpView({ data }) {
1946
+ if (data.mode === "detail" && data.entry) {
1947
+ const { usage, description, psqlAlias, detail, example } = data.entry;
1948
+ return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginTop: 1, children: [
1949
+ /* @__PURE__ */ jsxs5(Text5, { bold: true, color: "white", children: [
1950
+ usage,
1951
+ psqlAlias ? ` ${psqlAlias}` : ""
1952
+ ] }),
1953
+ /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text5, { children: description }) }),
1954
+ detail && /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: detail }) }),
1955
+ example && /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, children: [
1956
+ /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "Example: " }),
1957
+ /* @__PURE__ */ jsx5(Text5, { color: theme.accent, children: example })
1958
+ ] })
1959
+ ] });
1960
+ }
1961
+ if (data.mode === "list" && data.groups) {
1962
+ return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginTop: 1, children: [
1963
+ data.groups.map((group, gi) => /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginBottom: gi < data.groups.length - 1 ? 1 : 0, children: [
1964
+ /* @__PURE__ */ jsx5(Text5, { bold: true, color: theme.accent, children: group.category }),
1965
+ group.entries.map((entry) => /* @__PURE__ */ jsxs5(Box5, { marginLeft: 2, children: [
1966
+ /* @__PURE__ */ jsx5(Text5, { color: "white", children: entry.usage.padEnd(USAGE_WIDTH) }),
1967
+ /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: entry.description }),
1968
+ entry.psqlAlias && /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
1969
+ " ",
1970
+ entry.psqlAlias
1971
+ ] })
1972
+ ] }, entry.name))
1973
+ ] }, group.category)),
1974
+ /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
1975
+ "/help ",
1976
+ "<command>",
1977
+ " for more details."
1978
+ ] }) })
1979
+ ] });
1980
+ }
1981
+ return null;
1982
+ }
1983
+
1984
+ // src/ui/components/ErdView.tsx
1985
+ import { Box as Box6, Text as Text6 } from "ink";
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
+
1763
2114
  // src/ui/components/App.tsx
1764
- import { Fragment as Fragment3, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
2115
+ import { Fragment as Fragment4, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1765
2116
  var PAGE_SIZE = 50;
1766
2117
  var PLACEHOLDER2 = "#a5b4fc";
1767
2118
  function activePageSize() {
@@ -1775,26 +2126,27 @@ function limitLines(s, n) {
1775
2126
  }
1776
2127
  function EntryView({ entry }) {
1777
2128
  const showAi = entry.aiResponse !== "" || entry.aiError !== null;
2129
+ const showErd = entry.erdData !== null;
1778
2130
  const isShell = entry.query.startsWith("!");
1779
2131
  const isCommand = !isShell && (entry.query.startsWith("/") || entry.query.startsWith("\\"));
1780
2132
  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: [
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: [
1784
2136
  label,
1785
2137
  " "
1786
2138
  ] }),
1787
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: entry.query })
2139
+ /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: entry.query })
1788
2140
  ] }),
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: [
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: [
1791
2143
  "\u2713 ",
1792
2144
  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 })
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 })
1798
2150
  ] }) })
1799
2151
  ] });
1800
2152
  }
@@ -1818,6 +2170,8 @@ function App({ connectionState, aiUrl: aiUrl2, aiModel: aiModel2, aiKey: aiKey2,
1818
2170
  const [aiError, setAiError] = useState3(null);
1819
2171
  const [shellOutput, setShellOutput] = useState3(null);
1820
2172
  const [isShellRunning, setIsShellRunning] = useState3(false);
2173
+ const [erdData, setErdData] = useState3(null);
2174
+ const [isErdLoading, setIsErdLoading] = useState3(false);
1821
2175
  const [completedEntries, setCompletedEntries] = useState3([]);
1822
2176
  const entryIdRef = useRef2(0);
1823
2177
  const aliasScope = connectionState.status === "connected" ? makeScope(connectionState.driver, connectionState.user, connectionState.host, connectionState.database) : "";
@@ -1883,8 +2237,29 @@ function App({ connectionState, aiUrl: aiUrl2, aiModel: aiModel2, aiKey: aiKey2,
1883
2237
  setIsStreaming(false);
1884
2238
  }
1885
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
+ }
1886
2260
  async function handleQuery(sql) {
1887
2261
  if (connectionState.status !== "connected") return;
2262
+ setErdData(null);
1888
2263
  setAiResponse("");
1889
2264
  setAiError(null);
1890
2265
  setCommandMessage(null);
@@ -1942,11 +2317,13 @@ function App({ connectionState, aiUrl: aiUrl2, aiModel: aiModel2, aiKey: aiKey2,
1942
2317
  page,
1943
2318
  aiResponse,
1944
2319
  aiError,
1945
- shellOutput
2320
+ shellOutput,
2321
+ erdData
1946
2322
  }
1947
2323
  ]);
1948
2324
  }
1949
2325
  async function handleShell(cmd) {
2326
+ setErdData(null);
1950
2327
  setShellOutput(null);
1951
2328
  setIsShellRunning(true);
1952
2329
  setCommandMessage(null);
@@ -1992,6 +2369,9 @@ function App({ connectionState, aiUrl: aiUrl2, aiModel: aiModel2, aiKey: aiKey2,
1992
2369
  onChangeDatabase?.(db);
1993
2370
  },
1994
2371
  onExport: handleExport,
2372
+ onErd: () => {
2373
+ void handleErd();
2374
+ },
1995
2375
  onClear: () => {
1996
2376
  process.stdout.write("\x1B[2J\x1B[3J\x1B[H");
1997
2377
  setCompletedEntries([]);
@@ -2010,50 +2390,52 @@ function App({ connectionState, aiUrl: aiUrl2, aiModel: aiModel2, aiKey: aiKey2,
2010
2390
  });
2011
2391
  if (result.cleared) return;
2012
2392
  setLastQuery(sql);
2393
+ setErdData(null);
2013
2394
  setAiResponse("");
2014
2395
  setAiError(null);
2015
2396
  setElapsed(null);
2016
2397
  setPage(0);
2017
2398
  setQueryState({ status: "idle" });
2018
- if (result.message) setCommandMessage({ ok: result.ok, text: result.message });
2399
+ if (result.helpData) setCommandMessage({ ok: result.ok, text: "", helpData: result.helpData });
2400
+ else if (result.message) setCommandMessage({ ok: result.ok, text: result.message });
2019
2401
  else setCommandMessage(null);
2020
2402
  return;
2021
2403
  }
2022
2404
  void handleQuery(sql);
2023
2405
  }
2024
- const isLoading = queryState.status === "running" || isStreaming || isShellRunning;
2406
+ const isLoading = queryState.status === "running" || isStreaming || isShellRunning || isErdLoading;
2025
2407
  const isConnected = connectionState.status === "connected";
2026
2408
  const showAi = aiResponse !== "" || isStreaming || aiError !== null;
2027
2409
  const isShellEntry = lastQuery.startsWith("!");
2028
2410
  const isCommand = !isShellEntry && (lastQuery.startsWith("/") || lastQuery.startsWith("\\"));
2029
2411
  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: [
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: [
2037
2419
  activeLabel,
2038
2420
  " "
2039
2421
  ] }),
2040
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: lastQuery })
2422
+ /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: lastQuery })
2041
2423
  ] }),
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: [
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: [
2044
2426
  "\u2713 ",
2045
2427
  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: [
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: [
2050
2432
  aiResponse,
2051
- isStreaming && /* @__PURE__ */ jsx5(Text5, { color: PLACEHOLDER2, children: "\u258B" })
2433
+ isStreaming && /* @__PURE__ */ jsx7(Text7, { color: PLACEHOLDER2, children: "\u258B" })
2052
2434
  ] }) })
2053
- ] }) : !commandMessage && /* @__PURE__ */ jsx5(QueryResult, { state: queryState, elapsed, page, pageSize: activePageSize() })
2435
+ ] }) : !commandMessage && /* @__PURE__ */ jsx7(QueryResult, { state: queryState, elapsed, page, pageSize: activePageSize() })
2054
2436
  ] }) })
2055
2437
  ] }),
2056
- isConnected ? /* @__PURE__ */ jsx5(
2438
+ isConnected ? /* @__PURE__ */ jsx7(
2057
2439
  QueryInput,
2058
2440
  {
2059
2441
  onSubmit: handleSubmit,
@@ -2065,16 +2447,16 @@ function App({ connectionState, aiUrl: aiUrl2, aiModel: aiModel2, aiKey: aiKey2,
2065
2447
  aliases,
2066
2448
  schema: schema ?? void 0
2067
2449
  }
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}]` : "" }) })
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}]` : "" }) })
2070
2452
  ] })
2071
2453
  ] });
2072
2454
  }
2073
2455
 
2074
2456
  // src/ui/components/ConnectionWizard.tsx
2075
2457
  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";
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";
2078
2460
  var DRIVERS = ["postgresql", "mysql", "sqlite"];
2079
2461
  var LABEL_WIDTH = 10;
2080
2462
  function fieldLabels(driver) {
@@ -2229,36 +2611,36 @@ function ConnectionWizard({ onConnect, initialError }) {
2229
2611
  { isActive: isRawModeSupported }
2230
2612
  );
2231
2613
  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" }) }),
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" }) }),
2234
2616
  labels.map((label, idx) => {
2235
2617
  const isFocused = focus === idx;
2236
2618
  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: [
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: [
2242
2624
  fields.driver === d ? "\u25CF " : "\u25CB ",
2243
2625
  d.charAt(0).toUpperCase() + d.slice(1)
2244
2626
  ] })
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)" })
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)" })
2249
2631
  ] })
2250
2632
  ] }, label);
2251
2633
  }),
2252
- /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: connecting ? /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "Connecting\u2026" }) : error ? /* @__PURE__ */ jsxs6(Text6, { 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: [
2253
2635
  "\u2717 ",
2254
2636
  error
2255
2637
  ] }) : 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" }) })
2638
+ /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "Tab \xB7 \u2191\u2193 navigate Enter connect \u2190\u2192 cycle driver" }) })
2257
2639
  ] });
2258
2640
  }
2259
2641
 
2260
2642
  // src/index.tsx
2261
- import { jsx as jsx7 } from "react/jsx-runtime";
2643
+ import { jsx as jsx9 } from "react/jsx-runtime";
2262
2644
  var { values } = parseArgs({
2263
2645
  args: process.argv.slice(2).filter((a) => a !== "--"),
2264
2646
  options: {
@@ -2288,10 +2670,10 @@ function Root({ initialDsn: initialDsn2, aiUrl: aiUrl2, aiModel: aiModel2, aiKey
2288
2670
  }
2289
2671
  }, []);
2290
2672
  if (initialDsn2 && !connectionState && !dsnError) {
2291
- return /* @__PURE__ */ jsx7(Box7, { paddingX: 2, paddingTop: 1, children: /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "Connecting\u2026" }) });
2673
+ return /* @__PURE__ */ jsx9(Box9, { paddingX: 2, paddingTop: 1, children: /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "Connecting\u2026" }) });
2292
2674
  }
2293
2675
  if (!connectionState) {
2294
- return /* @__PURE__ */ jsx7(ConnectionWizard, { onConnect: setConnectionState, initialError: dsnError ?? void 0 });
2676
+ return /* @__PURE__ */ jsx9(ConnectionWizard, { onConnect: setConnectionState, initialError: dsnError ?? void 0 });
2295
2677
  }
2296
2678
  async function handleChangeDatabase(database) {
2297
2679
  if (connectionState.status !== "connected") return;
@@ -2301,7 +2683,7 @@ function Root({ initialDsn: initialDsn2, aiUrl: aiUrl2, aiModel: aiModel2, aiKey
2301
2683
  const next = await connectParams({ ...current.params, database });
2302
2684
  setConnectionState(next);
2303
2685
  }
2304
- return /* @__PURE__ */ jsx7(
2686
+ return /* @__PURE__ */ jsx9(
2305
2687
  App,
2306
2688
  {
2307
2689
  connectionState,
@@ -2315,6 +2697,6 @@ function Root({ initialDsn: initialDsn2, aiUrl: aiUrl2, aiModel: aiModel2, aiKey
2315
2697
  );
2316
2698
  }
2317
2699
  render(
2318
- /* @__PURE__ */ jsx7(Root, { initialDsn, aiUrl, aiModel, aiKey }),
2700
+ /* @__PURE__ */ jsx9(Root, { initialDsn, aiUrl, aiModel, aiKey }),
2319
2701
  { exitOnCtrlC: true }
2320
2702
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deaquinodev/querky",
3
- "version": "0.4.2",
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": [