sandal-db 1.0.2 → 1.0.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.
- package/dist/cli.js +155 -91
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -5,7 +5,7 @@ import fs4 from "node:fs";
|
|
|
5
5
|
import { Command } from "commander";
|
|
6
6
|
import chalk4 from "chalk";
|
|
7
7
|
import ora2 from "ora";
|
|
8
|
-
import { input
|
|
8
|
+
import { input, select, confirm } from "@inquirer/prompts";
|
|
9
9
|
|
|
10
10
|
// src/config/index.ts
|
|
11
11
|
import fs from "node:fs";
|
|
@@ -965,7 +965,7 @@ function createChatModel(options) {
|
|
|
965
965
|
}
|
|
966
966
|
|
|
967
967
|
// src/repl.ts
|
|
968
|
-
import
|
|
968
|
+
import readline2 from "node:readline";
|
|
969
969
|
import fs3 from "node:fs";
|
|
970
970
|
import chalk3 from "chalk";
|
|
971
971
|
import boxen2 from "boxen";
|
|
@@ -1382,10 +1382,42 @@ function classifyMongoQuery(rawText, query, rowThreshold, options) {
|
|
|
1382
1382
|
}
|
|
1383
1383
|
|
|
1384
1384
|
// src/safety/confirm.ts
|
|
1385
|
+
import readline from "node:readline";
|
|
1385
1386
|
import chalk from "chalk";
|
|
1386
1387
|
import boxen from "boxen";
|
|
1387
|
-
|
|
1388
|
-
|
|
1388
|
+
async function defaultAsk(promptText) {
|
|
1389
|
+
process.stdin.resume();
|
|
1390
|
+
const rl = readline.createInterface({
|
|
1391
|
+
input: process.stdin,
|
|
1392
|
+
output: process.stdout
|
|
1393
|
+
});
|
|
1394
|
+
return new Promise((resolve) => {
|
|
1395
|
+
rl.question(promptText, (answer) => {
|
|
1396
|
+
rl.close();
|
|
1397
|
+
process.stdin.resume();
|
|
1398
|
+
resolve(answer);
|
|
1399
|
+
});
|
|
1400
|
+
});
|
|
1401
|
+
}
|
|
1402
|
+
async function promptConfirm(message, defaultValue, ask = defaultAsk) {
|
|
1403
|
+
const suffix = defaultValue ? chalk.gray(" (Y/n): ") : chalk.gray(" (y/N): ");
|
|
1404
|
+
const response = (await ask(`${message}${suffix}`)).trim().toLowerCase();
|
|
1405
|
+
if (!response) {
|
|
1406
|
+
return defaultValue;
|
|
1407
|
+
}
|
|
1408
|
+
if (["y", "yes", "true", "1"].includes(response)) {
|
|
1409
|
+
return true;
|
|
1410
|
+
}
|
|
1411
|
+
if (["n", "no", "false", "0"].includes(response)) {
|
|
1412
|
+
return false;
|
|
1413
|
+
}
|
|
1414
|
+
return defaultValue;
|
|
1415
|
+
}
|
|
1416
|
+
async function promptInput(message, ask = defaultAsk) {
|
|
1417
|
+
const response = await ask(message);
|
|
1418
|
+
return response.trim();
|
|
1419
|
+
}
|
|
1420
|
+
async function requestUserConfirmation(query, safety, affectedRows, ask = defaultAsk) {
|
|
1389
1421
|
const queryDisplay = (query.sql || query.rawDisplay || "").trim();
|
|
1390
1422
|
if (safety.isFullWipe) {
|
|
1391
1423
|
const boxContent = [
|
|
@@ -1404,9 +1436,10 @@ async function requestUserConfirmation(query, safety, affectedRows) {
|
|
|
1404
1436
|
margin: { top: 1, bottom: 1 }
|
|
1405
1437
|
})
|
|
1406
1438
|
);
|
|
1407
|
-
const typed = await
|
|
1408
|
-
|
|
1409
|
-
|
|
1439
|
+
const typed = await promptInput(
|
|
1440
|
+
chalk.red.bold(`Type "${safety.literalWord || "DROP DATABASE"}" to confirm full wipe: `),
|
|
1441
|
+
ask
|
|
1442
|
+
);
|
|
1410
1443
|
if (typed.trim() === safety.literalWord) {
|
|
1411
1444
|
return { confirmed: true };
|
|
1412
1445
|
}
|
|
@@ -1443,11 +1476,12 @@ async function requestUserConfirmation(query, safety, affectedRows) {
|
|
|
1443
1476
|
})
|
|
1444
1477
|
);
|
|
1445
1478
|
if (safety.requiresLiteralWord && safety.literalWord) {
|
|
1446
|
-
const typed = await
|
|
1447
|
-
|
|
1448
|
-
`Destructive operation with no filter. Type "${safety.literalWord}" to confirm
|
|
1449
|
-
)
|
|
1450
|
-
|
|
1479
|
+
const typed = await promptInput(
|
|
1480
|
+
chalk.hex("#FFA500").bold(
|
|
1481
|
+
`Destructive operation with no filter. Type "${safety.literalWord}" to confirm: `
|
|
1482
|
+
),
|
|
1483
|
+
ask
|
|
1484
|
+
);
|
|
1451
1485
|
if (typed.trim() === safety.literalWord) {
|
|
1452
1486
|
return { confirmed: true };
|
|
1453
1487
|
}
|
|
@@ -1456,10 +1490,11 @@ async function requestUserConfirmation(query, safety, affectedRows) {
|
|
|
1456
1490
|
reason: `Confirmation word mismatch (expected "${safety.literalWord}"). Operation cancelled.`
|
|
1457
1491
|
};
|
|
1458
1492
|
}
|
|
1459
|
-
const answer2 = await
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1493
|
+
const answer2 = await promptConfirm(
|
|
1494
|
+
chalk.hex("#FFA500").bold("Proceed with dangerous operation?"),
|
|
1495
|
+
false,
|
|
1496
|
+
ask
|
|
1497
|
+
);
|
|
1463
1498
|
return { confirmed: answer2 };
|
|
1464
1499
|
}
|
|
1465
1500
|
if (safety.isStructural) {
|
|
@@ -1491,10 +1526,11 @@ async function requestUserConfirmation(query, safety, affectedRows) {
|
|
|
1491
1526
|
margin: { top: 1, bottom: 1 }
|
|
1492
1527
|
})
|
|
1493
1528
|
);
|
|
1494
|
-
const answer2 = await
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1529
|
+
const answer2 = await promptConfirm(
|
|
1530
|
+
chalk.cyan.bold("Apply this structural change?"),
|
|
1531
|
+
true,
|
|
1532
|
+
ask
|
|
1533
|
+
);
|
|
1498
1534
|
return { confirmed: answer2 };
|
|
1499
1535
|
}
|
|
1500
1536
|
if (safety.category === "write") {
|
|
@@ -1514,10 +1550,11 @@ async function requestUserConfirmation(query, safety, affectedRows) {
|
|
|
1514
1550
|
margin: { top: 1, bottom: 1 }
|
|
1515
1551
|
})
|
|
1516
1552
|
);
|
|
1517
|
-
const answer2 = await
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1553
|
+
const answer2 = await promptConfirm(
|
|
1554
|
+
chalk.magenta.bold("Execute write query?"),
|
|
1555
|
+
false,
|
|
1556
|
+
ask
|
|
1557
|
+
);
|
|
1521
1558
|
return { confirmed: answer2 };
|
|
1522
1559
|
}
|
|
1523
1560
|
const lines = [
|
|
@@ -1533,10 +1570,11 @@ async function requestUserConfirmation(query, safety, affectedRows) {
|
|
|
1533
1570
|
margin: { top: 1, bottom: 1 }
|
|
1534
1571
|
})
|
|
1535
1572
|
);
|
|
1536
|
-
const answer = await
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1573
|
+
const answer = await promptConfirm(
|
|
1574
|
+
chalk.green.bold("Execute read query?"),
|
|
1575
|
+
true,
|
|
1576
|
+
ask
|
|
1577
|
+
);
|
|
1540
1578
|
return { confirmed: answer };
|
|
1541
1579
|
}
|
|
1542
1580
|
|
|
@@ -1668,8 +1706,8 @@ var OBVIOUS_DATABASE_PATTERNS = [
|
|
|
1668
1706
|
/\b(?:how many (?:users|rows|records|orders|items|products|documents))\b/i,
|
|
1669
1707
|
/\b(?:database|postgres|mongodb|mongo|pg_)\b/i
|
|
1670
1708
|
];
|
|
1671
|
-
async function classifyIntent(
|
|
1672
|
-
const trimmed =
|
|
1709
|
+
async function classifyIntent(input2, model, historyContext) {
|
|
1710
|
+
const trimmed = input2.trim();
|
|
1673
1711
|
for (const pattern of OBVIOUS_OUT_OF_SCOPE_PATTERNS) {
|
|
1674
1712
|
if (pattern.test(trimmed)) {
|
|
1675
1713
|
return {
|
|
@@ -1922,7 +1960,8 @@ Do not wrap with markdown or code fences.`;
|
|
|
1922
1960
|
if (!state.generatedQuery || !state.safety) {
|
|
1923
1961
|
return { confirmed: false, cancelReason: "Missing query or safety classification." };
|
|
1924
1962
|
}
|
|
1925
|
-
const
|
|
1963
|
+
const confirmHandler = config.confirmFn || requestUserConfirmation;
|
|
1964
|
+
const result = await confirmHandler(
|
|
1926
1965
|
state.generatedQuery,
|
|
1927
1966
|
state.safety,
|
|
1928
1967
|
state.affectedRowCount ?? null
|
|
@@ -2058,9 +2097,9 @@ function createMarkdownRenderer(options = {}) {
|
|
|
2058
2097
|
codespan: chalk2.yellow,
|
|
2059
2098
|
code: chalk2.yellow,
|
|
2060
2099
|
blockquote: chalk2.gray.italic,
|
|
2061
|
-
hr: (
|
|
2100
|
+
hr: (input2) => {
|
|
2062
2101
|
const w = Math.min(options.width ?? (process.stdout.columns || 80), 80);
|
|
2063
|
-
return chalk2.gray(
|
|
2102
|
+
return chalk2.gray(input2 && input2.trim() ? input2 : "\u2500".repeat(w));
|
|
2064
2103
|
},
|
|
2065
2104
|
table: chalk2.reset,
|
|
2066
2105
|
tableOptions: {
|
|
@@ -2312,6 +2351,8 @@ var ReplSession = class {
|
|
|
2312
2351
|
agent;
|
|
2313
2352
|
memoryManager;
|
|
2314
2353
|
classifierOptions;
|
|
2354
|
+
rl;
|
|
2355
|
+
currentSpinner;
|
|
2315
2356
|
constructor(options) {
|
|
2316
2357
|
this.adapter = options.adapter;
|
|
2317
2358
|
this.model = options.model;
|
|
@@ -2327,11 +2368,7 @@ var ReplSession = class {
|
|
|
2327
2368
|
allowFullWipe: this.allowFullWipe,
|
|
2328
2369
|
rowThresholdForDangerousUpdate: this.rowThreshold
|
|
2329
2370
|
};
|
|
2330
|
-
this.
|
|
2331
|
-
adapter: this.adapter,
|
|
2332
|
-
model: this.model,
|
|
2333
|
-
classifierOptions: this.classifierOptions
|
|
2334
|
-
});
|
|
2371
|
+
this.initAgent();
|
|
2335
2372
|
this.memoryManager = new ChatMemoryManager();
|
|
2336
2373
|
const initialSession = this.memoryManager.createSession("New Chat", this.adapter.connectionUrl);
|
|
2337
2374
|
this.sessionId = initialSession.id;
|
|
@@ -2339,23 +2376,57 @@ var ReplSession = class {
|
|
|
2339
2376
|
addSavedConnection(this.adapter.connectionUrl);
|
|
2340
2377
|
}
|
|
2341
2378
|
}
|
|
2379
|
+
initAgent() {
|
|
2380
|
+
this.agent = createDatabaseAgent({
|
|
2381
|
+
adapter: this.adapter,
|
|
2382
|
+
model: this.model,
|
|
2383
|
+
classifierOptions: this.classifierOptions,
|
|
2384
|
+
confirmFn: async (query, safety, affectedRows) => {
|
|
2385
|
+
if (this.currentSpinner && this.currentSpinner.isSpinning) {
|
|
2386
|
+
this.currentSpinner.stop();
|
|
2387
|
+
}
|
|
2388
|
+
const confirmResult = await requestUserConfirmation(
|
|
2389
|
+
query,
|
|
2390
|
+
safety,
|
|
2391
|
+
affectedRows,
|
|
2392
|
+
this.askQuestion.bind(this)
|
|
2393
|
+
);
|
|
2394
|
+
if (confirmResult.confirmed && this.currentSpinner) {
|
|
2395
|
+
this.currentSpinner.text = chalk3.cyan("Executing query and analyzing...");
|
|
2396
|
+
this.currentSpinner.start();
|
|
2397
|
+
}
|
|
2398
|
+
return confirmResult;
|
|
2399
|
+
}
|
|
2400
|
+
});
|
|
2401
|
+
}
|
|
2402
|
+
askQuestion(query) {
|
|
2403
|
+
process.stdin.resume();
|
|
2404
|
+
if (!this.rl || this.rl.closed) {
|
|
2405
|
+
this.rl = readline2.createInterface({
|
|
2406
|
+
input: process.stdin,
|
|
2407
|
+
output: process.stdout,
|
|
2408
|
+
terminal: true
|
|
2409
|
+
});
|
|
2410
|
+
}
|
|
2411
|
+
return new Promise((resolve) => this.rl.question(query, resolve));
|
|
2412
|
+
}
|
|
2342
2413
|
getPromptText() {
|
|
2343
2414
|
return chalk3.cyan(`sandal [${this.adapter.type}]> `);
|
|
2344
2415
|
}
|
|
2345
2416
|
async start() {
|
|
2346
2417
|
this.setupSignalHandlers();
|
|
2347
2418
|
this.printWelcomeBanner();
|
|
2348
|
-
|
|
2419
|
+
process.stdin.resume();
|
|
2420
|
+
this.rl = readline2.createInterface({
|
|
2349
2421
|
input: process.stdin,
|
|
2350
2422
|
output: process.stdout,
|
|
2351
2423
|
terminal: true
|
|
2352
2424
|
});
|
|
2353
|
-
const askQuestion = (
|
|
2354
|
-
return new Promise((resolve) => rl.question(query, resolve));
|
|
2355
|
-
};
|
|
2425
|
+
const askQuestion = this.askQuestion.bind(this);
|
|
2356
2426
|
while (this.isRunning) {
|
|
2357
|
-
|
|
2358
|
-
const
|
|
2427
|
+
process.stdin.resume();
|
|
2428
|
+
const input2 = await askQuestion(this.getPromptText());
|
|
2429
|
+
const trimmed = input2.trim();
|
|
2359
2430
|
if (!trimmed) continue;
|
|
2360
2431
|
if (["exit", "quit", ".exit", ".quit"].includes(trimmed.toLowerCase())) {
|
|
2361
2432
|
break;
|
|
@@ -2462,6 +2533,7 @@ ${chalk3.gray(
|
|
|
2462
2533
|
continue;
|
|
2463
2534
|
}
|
|
2464
2535
|
const agentSpinner = ora(chalk3.cyan("Agent planning query...")).start();
|
|
2536
|
+
this.currentSpinner = agentSpinner;
|
|
2465
2537
|
try {
|
|
2466
2538
|
const historyMessages = this.memoryManager.toLangChainMessages(this.sessionId, 6);
|
|
2467
2539
|
const result = await this.agent.invoke(
|
|
@@ -2475,7 +2547,9 @@ ${chalk3.gray(
|
|
|
2475
2547
|
}
|
|
2476
2548
|
}
|
|
2477
2549
|
);
|
|
2478
|
-
agentSpinner.
|
|
2550
|
+
if (agentSpinner.isSpinning) {
|
|
2551
|
+
agentSpinner.stop();
|
|
2552
|
+
}
|
|
2479
2553
|
if (result.queryResult && result.queryResult.success && result.queryResult.rows) {
|
|
2480
2554
|
this.renderRowsTable(result.queryResult);
|
|
2481
2555
|
}
|
|
@@ -2506,10 +2580,24 @@ ${chalk3.gray(
|
|
|
2506
2580
|
);
|
|
2507
2581
|
}
|
|
2508
2582
|
} catch (err) {
|
|
2509
|
-
agentSpinner.
|
|
2583
|
+
if (agentSpinner.isSpinning) {
|
|
2584
|
+
agentSpinner.fail(chalk3.red(`Execution failed: ${err.message}`));
|
|
2585
|
+
} else {
|
|
2586
|
+
console.error(chalk3.red(`
|
|
2587
|
+
Execution failed: ${err.message}
|
|
2588
|
+
`));
|
|
2589
|
+
}
|
|
2590
|
+
} finally {
|
|
2591
|
+
if (agentSpinner.isSpinning) {
|
|
2592
|
+
agentSpinner.stop();
|
|
2593
|
+
}
|
|
2594
|
+
this.currentSpinner = void 0;
|
|
2595
|
+
process.stdin.resume();
|
|
2510
2596
|
}
|
|
2511
2597
|
}
|
|
2512
|
-
rl.
|
|
2598
|
+
if (this.rl && !this.rl.closed) {
|
|
2599
|
+
this.rl.close();
|
|
2600
|
+
}
|
|
2513
2601
|
await this.shutdown();
|
|
2514
2602
|
}
|
|
2515
2603
|
async handleSwitchConnection(targetUrl, ask) {
|
|
@@ -2566,11 +2654,7 @@ ${chalk3.gray(
|
|
|
2566
2654
|
} catch {
|
|
2567
2655
|
}
|
|
2568
2656
|
this.adapter = newAdapter;
|
|
2569
|
-
this.
|
|
2570
|
-
adapter: this.adapter,
|
|
2571
|
-
model: this.model,
|
|
2572
|
-
classifierOptions: this.classifierOptions
|
|
2573
|
-
});
|
|
2657
|
+
this.initAgent();
|
|
2574
2658
|
addSavedConnection(urlToConnect);
|
|
2575
2659
|
spinner.succeed(
|
|
2576
2660
|
chalk3.green(
|
|
@@ -2611,17 +2695,17 @@ Current Model: `) + chalk3.white(this.modelName) + chalk3.gray(` (${this.provide
|
|
|
2611
2695
|
});
|
|
2612
2696
|
console.log(chalk3.gray(" [O] Other (type custom model name)"));
|
|
2613
2697
|
console.log(chalk3.gray(" [C] Cancel\n"));
|
|
2614
|
-
const
|
|
2615
|
-
if (!
|
|
2616
|
-
const num = parseInt(
|
|
2698
|
+
const input2 = (await ask(chalk3.cyan("Select model [number, name, C]: "))).trim();
|
|
2699
|
+
if (!input2 || input2.toLowerCase() === "c") return;
|
|
2700
|
+
const num = parseInt(input2, 10);
|
|
2617
2701
|
if (!isNaN(num) && num >= 1 && num <= list.length) {
|
|
2618
2702
|
targetModel = list[num - 1];
|
|
2619
|
-
} else if (
|
|
2703
|
+
} else if (input2.toLowerCase() === "o") {
|
|
2620
2704
|
const custom = (await ask(chalk3.cyan("Enter model name: "))).trim();
|
|
2621
2705
|
if (!custom) return;
|
|
2622
2706
|
targetModel = custom;
|
|
2623
2707
|
} else {
|
|
2624
|
-
targetModel =
|
|
2708
|
+
targetModel = input2;
|
|
2625
2709
|
}
|
|
2626
2710
|
}
|
|
2627
2711
|
if (!this.apiKey) {
|
|
@@ -2645,11 +2729,7 @@ No API key found for ${this.provider.toUpperCase()}.`));
|
|
|
2645
2729
|
apiKey: this.apiKey
|
|
2646
2730
|
});
|
|
2647
2731
|
this.modelName = targetModel;
|
|
2648
|
-
this.
|
|
2649
|
-
adapter: this.adapter,
|
|
2650
|
-
model: this.model,
|
|
2651
|
-
classifierOptions: this.classifierOptions
|
|
2652
|
-
});
|
|
2732
|
+
this.initAgent();
|
|
2653
2733
|
console.log(chalk3.green(`
|
|
2654
2734
|
Updated active model to: ${targetModel} [${this.provider}]
|
|
2655
2735
|
`));
|
|
@@ -2706,11 +2786,7 @@ No API key found for ${p.toUpperCase()}.`));
|
|
|
2706
2786
|
this.provider = p;
|
|
2707
2787
|
this.modelName = defaultModel;
|
|
2708
2788
|
this.apiKey = key;
|
|
2709
|
-
this.
|
|
2710
|
-
adapter: this.adapter,
|
|
2711
|
-
model: this.model,
|
|
2712
|
-
classifierOptions: this.classifierOptions
|
|
2713
|
-
});
|
|
2789
|
+
this.initAgent();
|
|
2714
2790
|
console.log(
|
|
2715
2791
|
chalk3.green(`
|
|
2716
2792
|
Switched provider to ${p.toUpperCase()} with model ${defaultModel}.
|
|
@@ -2753,11 +2829,7 @@ Removed stored API key for "${this.provider}" from ~/.sandal/config.json.`));
|
|
|
2753
2829
|
model: this.modelName,
|
|
2754
2830
|
apiKey: newKey
|
|
2755
2831
|
});
|
|
2756
|
-
this.
|
|
2757
|
-
adapter: this.adapter,
|
|
2758
|
-
model: this.model,
|
|
2759
|
-
classifierOptions: this.classifierOptions
|
|
2760
|
-
});
|
|
2832
|
+
this.initAgent();
|
|
2761
2833
|
console.log(chalk3.green(`
|
|
2762
2834
|
Updated API key for "${this.provider}" and refreshed model.
|
|
2763
2835
|
`));
|
|
@@ -2794,11 +2866,7 @@ Updated API key for "${this.provider}" and refreshed model.
|
|
|
2794
2866
|
handleNewChat() {
|
|
2795
2867
|
const newSession = this.memoryManager.createSession("New Chat", this.adapter.connectionUrl);
|
|
2796
2868
|
this.sessionId = newSession.id;
|
|
2797
|
-
this.
|
|
2798
|
-
adapter: this.adapter,
|
|
2799
|
-
model: this.model,
|
|
2800
|
-
classifierOptions: this.classifierOptions
|
|
2801
|
-
});
|
|
2869
|
+
this.initAgent();
|
|
2802
2870
|
console.log(chalk3.green(`
|
|
2803
2871
|
Started fresh chat session: ${this.sessionId}`));
|
|
2804
2872
|
console.log(chalk3.gray("Chat memory is clean for this new session.\n"));
|
|
@@ -2829,11 +2897,7 @@ Could not load chat session "${targetId}".
|
|
|
2829
2897
|
return;
|
|
2830
2898
|
}
|
|
2831
2899
|
this.sessionId = session.id;
|
|
2832
|
-
this.
|
|
2833
|
-
adapter: this.adapter,
|
|
2834
|
-
model: this.model,
|
|
2835
|
-
classifierOptions: this.classifierOptions
|
|
2836
|
-
});
|
|
2900
|
+
this.initAgent();
|
|
2837
2901
|
console.log(
|
|
2838
2902
|
chalk3.green(
|
|
2839
2903
|
`
|
|
@@ -2871,11 +2935,7 @@ Chat History: "${session.title}" [${this.sessionId}]:`));
|
|
|
2871
2935
|
}
|
|
2872
2936
|
handleClearChat() {
|
|
2873
2937
|
this.memoryManager.clearSession(this.sessionId);
|
|
2874
|
-
this.
|
|
2875
|
-
adapter: this.adapter,
|
|
2876
|
-
model: this.model,
|
|
2877
|
-
classifierOptions: this.classifierOptions
|
|
2878
|
-
});
|
|
2938
|
+
this.initAgent();
|
|
2879
2939
|
console.log(chalk3.green(`
|
|
2880
2940
|
Chat memory for session [${this.sessionId}] has been cleared.
|
|
2881
2941
|
`));
|
|
@@ -3102,6 +3162,9 @@ Schema for collection: ${collection.name}`));
|
|
|
3102
3162
|
process.on("SIGTERM", handleExit);
|
|
3103
3163
|
}
|
|
3104
3164
|
async shutdown() {
|
|
3165
|
+
if (this.rl && !this.rl.closed) {
|
|
3166
|
+
this.rl.close();
|
|
3167
|
+
}
|
|
3105
3168
|
if (this.adapter.isConnected()) {
|
|
3106
3169
|
try {
|
|
3107
3170
|
await this.adapter.disconnect();
|
|
@@ -3351,7 +3414,7 @@ async function runCli(opts) {
|
|
|
3351
3414
|
"\nNo database connection URL found in CLI flags, environment, or config."
|
|
3352
3415
|
)
|
|
3353
3416
|
);
|
|
3354
|
-
dbUrl = await
|
|
3417
|
+
dbUrl = await input({
|
|
3355
3418
|
message: "Enter your database URL (postgres://... or mongodb://...):",
|
|
3356
3419
|
validate: (val) => {
|
|
3357
3420
|
try {
|
|
@@ -3362,7 +3425,7 @@ async function runCli(opts) {
|
|
|
3362
3425
|
}
|
|
3363
3426
|
}
|
|
3364
3427
|
});
|
|
3365
|
-
const saveDb = await
|
|
3428
|
+
const saveDb = await confirm({
|
|
3366
3429
|
message: "Save this database URL to ~/.sandal/config.json for future runs?",
|
|
3367
3430
|
default: true
|
|
3368
3431
|
});
|
|
@@ -3387,11 +3450,11 @@ async function runCli(opts) {
|
|
|
3387
3450
|
{ name: "Anthropic (ANTHROPIC_API_KEY)", value: "anthropic" }
|
|
3388
3451
|
]
|
|
3389
3452
|
});
|
|
3390
|
-
apiKey = await
|
|
3453
|
+
apiKey = await input({
|
|
3391
3454
|
message: `Enter your ${provider.toUpperCase()} API key:`,
|
|
3392
3455
|
validate: (val) => val.trim().length > 0 ? true : "API key cannot be empty."
|
|
3393
3456
|
});
|
|
3394
|
-
const saveKey = await
|
|
3457
|
+
const saveKey = await confirm({
|
|
3395
3458
|
message: "Save this API key to ~/.sandal/config.json?",
|
|
3396
3459
|
default: true
|
|
3397
3460
|
});
|
|
@@ -3426,6 +3489,7 @@ async function runCli(opts) {
|
|
|
3426
3489
|
model: modelName,
|
|
3427
3490
|
apiKey
|
|
3428
3491
|
});
|
|
3492
|
+
process.stdin.resume();
|
|
3429
3493
|
const repl = new ReplSession({
|
|
3430
3494
|
adapter,
|
|
3431
3495
|
model,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sandal-db",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "SANDAL - Safe Agentic Natural-language Database Access Layer. Production-grade agentic database assistant CLI using LangGraph and LLMs (Google Gemini, OpenAI, Anthropic)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|