@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.
- package/dist/index.js +447 -65
- 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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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__ */
|
|
1782
|
-
/* @__PURE__ */
|
|
1783
|
-
/* @__PURE__ */
|
|
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__ */
|
|
2139
|
+
/* @__PURE__ */ jsx7(Text7, { dimColor: true, children: entry.query })
|
|
1788
2140
|
] }),
|
|
1789
|
-
/* @__PURE__ */
|
|
1790
|
-
entry.commandMessage && (entry.commandMessage.ok ? /* @__PURE__ */
|
|
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__ */
|
|
1794
|
-
showAi ? /* @__PURE__ */
|
|
1795
|
-
/* @__PURE__ */
|
|
1796
|
-
/* @__PURE__ */
|
|
1797
|
-
] }) : !entry.commandMessage && /* @__PURE__ */
|
|
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.
|
|
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__ */
|
|
2031
|
-
/* @__PURE__ */
|
|
2032
|
-
/* @__PURE__ */
|
|
2033
|
-
lastQuery === "" && /* @__PURE__ */
|
|
2034
|
-
lastQuery !== "" && /* @__PURE__ */
|
|
2035
|
-
/* @__PURE__ */
|
|
2036
|
-
/* @__PURE__ */
|
|
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__ */
|
|
2422
|
+
/* @__PURE__ */ jsx7(Text7, { dimColor: true, children: lastQuery })
|
|
2041
2423
|
] }),
|
|
2042
|
-
/* @__PURE__ */
|
|
2043
|
-
commandMessage && (commandMessage.ok ? /* @__PURE__ */
|
|
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__ */
|
|
2047
|
-
showAi ? /* @__PURE__ */
|
|
2048
|
-
/* @__PURE__ */
|
|
2049
|
-
/* @__PURE__ */
|
|
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__ */
|
|
2433
|
+
isStreaming && /* @__PURE__ */ jsx7(Text7, { color: PLACEHOLDER2, children: "\u258B" })
|
|
2052
2434
|
] }) })
|
|
2053
|
-
] }) : !commandMessage && /* @__PURE__ */
|
|
2435
|
+
] }) : !commandMessage && /* @__PURE__ */ jsx7(QueryResult, { state: queryState, elapsed, page, pageSize: activePageSize() })
|
|
2054
2436
|
] }) })
|
|
2055
2437
|
] }),
|
|
2056
|
-
isConnected ? /* @__PURE__ */
|
|
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__ */
|
|
2069
|
-
(vimEnabled || inputIsShell) && /* @__PURE__ */
|
|
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
|
|
2077
|
-
import { jsx as
|
|
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__ */
|
|
2233
|
-
/* @__PURE__ */
|
|
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__ */
|
|
2238
|
-
/* @__PURE__ */
|
|
2239
|
-
isDriver ? /* @__PURE__ */
|
|
2240
|
-
i > 0 && /* @__PURE__ */
|
|
2241
|
-
/* @__PURE__ */
|
|
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__ */
|
|
2246
|
-
/* @__PURE__ */
|
|
2247
|
-
isFocused && /* @__PURE__ */
|
|
2248
|
-
isFocused && isPassword(idx) && keychainHint && /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
2700
|
+
/* @__PURE__ */ jsx9(Root, { initialDsn, aiUrl, aiModel, aiKey }),
|
|
2319
2701
|
{ exitOnCtrlC: true }
|
|
2320
2702
|
);
|