postgresai 0.14.0-dev.36 → 0.14.0-dev.38
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/README.md +23 -23
- package/bin/postgres-ai.ts +357 -24
- package/dist/bin/postgres-ai.js +331 -23
- package/dist/bin/postgres-ai.js.map +1 -1
- package/dist/lib/checkup-api.d.ts +33 -0
- package/dist/lib/checkup-api.d.ts.map +1 -0
- package/dist/lib/checkup-api.js +187 -0
- package/dist/lib/checkup-api.js.map +1 -0
- package/dist/lib/checkup.d.ts +153 -0
- package/dist/lib/checkup.d.ts.map +1 -0
- package/dist/lib/checkup.js +536 -0
- package/dist/lib/checkup.js.map +1 -0
- package/dist/lib/config.d.ts +1 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +2 -0
- package/dist/lib/config.js.map +1 -1
- package/dist/package.json +1 -1
- package/lib/checkup-api.ts +177 -0
- package/lib/checkup.ts +622 -0
- package/lib/config.ts +3 -0
- package/package.json +1 -1
- package/reports/A002.json +23 -0
- package/reports/A003.json +3343 -0
- package/reports/A004.json +134 -0
- package/reports/A007.json +683 -0
- package/reports/A013.json +23 -0
- package/test/checkup.test.cjs +645 -0
- package/test/init.integration.test.cjs +10 -10
- package/test/init.test.cjs +73 -4
package/dist/bin/postgres-ai.js
CHANGED
|
@@ -51,8 +51,61 @@ const mcp_server_1 = require("../lib/mcp-server");
|
|
|
51
51
|
const issues_1 = require("../lib/issues");
|
|
52
52
|
const util_2 = require("../lib/util");
|
|
53
53
|
const init_1 = require("../lib/init");
|
|
54
|
+
const checkup_1 = require("../lib/checkup");
|
|
55
|
+
const checkup_api_1 = require("../lib/checkup-api");
|
|
54
56
|
const execPromise = (0, util_1.promisify)(child_process_1.exec);
|
|
55
57
|
const execFilePromise = (0, util_1.promisify)(child_process_1.execFile);
|
|
58
|
+
function expandHomePath(p) {
|
|
59
|
+
const s = (p || "").trim();
|
|
60
|
+
if (!s)
|
|
61
|
+
return s;
|
|
62
|
+
if (s === "~")
|
|
63
|
+
return os.homedir();
|
|
64
|
+
if (s.startsWith("~/") || s.startsWith("~\\")) {
|
|
65
|
+
return path.join(os.homedir(), s.slice(2));
|
|
66
|
+
}
|
|
67
|
+
return s;
|
|
68
|
+
}
|
|
69
|
+
function createTtySpinner(enabled, initialText) {
|
|
70
|
+
if (!enabled) {
|
|
71
|
+
return {
|
|
72
|
+
update: () => { },
|
|
73
|
+
stop: () => { },
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
const frames = ["|", "/", "-", "\\"];
|
|
77
|
+
const startTs = Date.now();
|
|
78
|
+
let text = initialText;
|
|
79
|
+
let frameIdx = 0;
|
|
80
|
+
let stopped = false;
|
|
81
|
+
const render = () => {
|
|
82
|
+
if (stopped)
|
|
83
|
+
return;
|
|
84
|
+
const elapsedSec = ((Date.now() - startTs) / 1000).toFixed(1);
|
|
85
|
+
const frame = frames[frameIdx % frames.length];
|
|
86
|
+
frameIdx += 1;
|
|
87
|
+
process.stdout.write(`\r\x1b[2K${frame} ${text} (${elapsedSec}s)`);
|
|
88
|
+
};
|
|
89
|
+
const timer = setInterval(render, 120);
|
|
90
|
+
render(); // immediate feedback
|
|
91
|
+
return {
|
|
92
|
+
update: (t) => {
|
|
93
|
+
text = t;
|
|
94
|
+
render();
|
|
95
|
+
},
|
|
96
|
+
stop: (finalText) => {
|
|
97
|
+
if (stopped)
|
|
98
|
+
return;
|
|
99
|
+
stopped = true;
|
|
100
|
+
clearInterval(timer);
|
|
101
|
+
process.stdout.write("\r\x1b[2K");
|
|
102
|
+
if (finalText && finalText.trim()) {
|
|
103
|
+
process.stdout.write(finalText);
|
|
104
|
+
}
|
|
105
|
+
process.stdout.write("\n");
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
}
|
|
56
109
|
function getDefaultMonitoringProjectDir() {
|
|
57
110
|
const override = process.env.PGAI_PROJECT_DIR;
|
|
58
111
|
if (override && override.trim())
|
|
@@ -175,8 +228,21 @@ program
|
|
|
175
228
|
.option("--api-base-url <url>", "API base URL for backend RPC (overrides PGAI_API_BASE_URL)")
|
|
176
229
|
.option("--ui-base-url <url>", "UI base URL for browser routes (overrides PGAI_UI_BASE_URL)");
|
|
177
230
|
program
|
|
178
|
-
.command("
|
|
179
|
-
.description("
|
|
231
|
+
.command("set-default-project <project>")
|
|
232
|
+
.description("store default project for checkup uploads")
|
|
233
|
+
.action(async (project) => {
|
|
234
|
+
const value = (project || "").trim();
|
|
235
|
+
if (!value) {
|
|
236
|
+
console.error("Error: project is required");
|
|
237
|
+
process.exitCode = 1;
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
config.writeConfig({ defaultProject: value });
|
|
241
|
+
console.log(`Default project saved: ${value}`);
|
|
242
|
+
});
|
|
243
|
+
program
|
|
244
|
+
.command("prepare-db [conn]")
|
|
245
|
+
.description("Prepare database for monitoring: create monitoring user, required view(s), and grant permissions (idempotent)")
|
|
180
246
|
.option("--db-url <url>", "PostgreSQL connection URL (admin) to run the setup against (deprecated; pass it as positional arg)")
|
|
181
247
|
.option("-h, --host <host>", "PostgreSQL host (psql-like)")
|
|
182
248
|
.option("-p, --port <port>", "PostgreSQL port (psql-like)")
|
|
@@ -193,9 +259,9 @@ program
|
|
|
193
259
|
.addHelpText("after", [
|
|
194
260
|
"",
|
|
195
261
|
"Examples:",
|
|
196
|
-
" postgresai
|
|
197
|
-
" postgresai
|
|
198
|
-
" postgresai
|
|
262
|
+
" postgresai prepare-db postgresql://admin@host:5432/dbname",
|
|
263
|
+
" postgresai prepare-db \"dbname=dbname host=host user=admin\"",
|
|
264
|
+
" postgresai prepare-db -h host -p 5432 -U admin -d dbname",
|
|
199
265
|
"",
|
|
200
266
|
"Admin password:",
|
|
201
267
|
" --admin-password <password> or PGPASSWORD=... (libpq standard)",
|
|
@@ -217,16 +283,16 @@ program
|
|
|
217
283
|
" PGAI_MON_PASSWORD — monitoring password",
|
|
218
284
|
"",
|
|
219
285
|
"Inspect SQL without applying changes:",
|
|
220
|
-
" postgresai
|
|
286
|
+
" postgresai prepare-db <conn> --print-sql",
|
|
221
287
|
"",
|
|
222
288
|
"Verify setup (no changes):",
|
|
223
|
-
" postgresai
|
|
289
|
+
" postgresai prepare-db <conn> --verify",
|
|
224
290
|
"",
|
|
225
291
|
"Reset monitoring password only:",
|
|
226
|
-
" postgresai
|
|
292
|
+
" postgresai prepare-db <conn> --reset-password --password '...'",
|
|
227
293
|
"",
|
|
228
294
|
"Offline SQL plan (no DB connection):",
|
|
229
|
-
" postgresai
|
|
295
|
+
" postgresai prepare-db --print-sql",
|
|
230
296
|
].join("\n"))
|
|
231
297
|
.action(async (conn, opts, cmd) => {
|
|
232
298
|
if (opts.verify && opts.resetPassword) {
|
|
@@ -285,7 +351,7 @@ program
|
|
|
285
351
|
}
|
|
286
352
|
catch (e) {
|
|
287
353
|
const msg = e instanceof Error ? e.message : String(e);
|
|
288
|
-
console.error(`Error:
|
|
354
|
+
console.error(`Error: prepare-db: ${msg}`);
|
|
289
355
|
// When connection details are missing, show full init help (options + examples).
|
|
290
356
|
if (typeof msg === "string" && msg.startsWith("Connection is required.")) {
|
|
291
357
|
console.error("");
|
|
@@ -316,7 +382,7 @@ program
|
|
|
316
382
|
includeOptionalPermissions,
|
|
317
383
|
});
|
|
318
384
|
if (v.ok) {
|
|
319
|
-
console.log("✓
|
|
385
|
+
console.log("✓ prepare-db verify: OK");
|
|
320
386
|
if (v.missingOptional.length > 0) {
|
|
321
387
|
console.log("⚠ Optional items missing:");
|
|
322
388
|
for (const m of v.missingOptional)
|
|
@@ -324,7 +390,7 @@ program
|
|
|
324
390
|
}
|
|
325
391
|
return;
|
|
326
392
|
}
|
|
327
|
-
console.error("✗
|
|
393
|
+
console.error("✗ prepare-db verify failed: missing required items");
|
|
328
394
|
for (const m of v.missingRequired)
|
|
329
395
|
console.error(`- ${m}`);
|
|
330
396
|
if (v.missingOptional.length > 0) {
|
|
@@ -396,7 +462,7 @@ program
|
|
|
396
462
|
return;
|
|
397
463
|
}
|
|
398
464
|
const { applied, skippedOptional } = await (0, init_1.applyInitPlan)({ client, plan: effectivePlan });
|
|
399
|
-
console.log(opts.resetPassword ? "✓
|
|
465
|
+
console.log(opts.resetPassword ? "✓ prepare-db password reset completed" : "✓ prepare-db completed");
|
|
400
466
|
if (skippedOptional.length > 0) {
|
|
401
467
|
console.log("⚠ Some optional steps were skipped (not supported or insufficient privileges):");
|
|
402
468
|
for (const s of skippedOptional)
|
|
@@ -422,7 +488,7 @@ program
|
|
|
422
488
|
if (!message || message === "[object Object]") {
|
|
423
489
|
message = "Unknown error";
|
|
424
490
|
}
|
|
425
|
-
console.error(`Error:
|
|
491
|
+
console.error(`Error: prepare-db: ${message}`);
|
|
426
492
|
// If this was a plan step failure, surface the step name explicitly to help users diagnose quickly.
|
|
427
493
|
const stepMatch = typeof message === "string" ? message.match(/Failed at step "([^"]+)":/i) : null;
|
|
428
494
|
const failedStep = stepMatch?.[1];
|
|
@@ -475,6 +541,233 @@ program
|
|
|
475
541
|
}
|
|
476
542
|
}
|
|
477
543
|
});
|
|
544
|
+
program
|
|
545
|
+
.command("checkup [conn]")
|
|
546
|
+
.description("generate health check reports directly from PostgreSQL (express mode)")
|
|
547
|
+
.option("--check-id <id>", `specific check to run: ${Object.keys(checkup_1.CHECK_INFO).join(", ")}, or ALL`, "ALL")
|
|
548
|
+
.option("--node-name <name>", "node name for reports", "node-01")
|
|
549
|
+
.option("--output <path>", "output directory for JSON files")
|
|
550
|
+
.option("--json", "output to stdout as JSON instead of files")
|
|
551
|
+
.option("--upload", "create a remote checkup report and upload JSON results (requires API key)", false)
|
|
552
|
+
.option("--project <project>", "project name or ID for remote upload (used with --upload; defaults to config defaultProject)")
|
|
553
|
+
.addHelpText("after", [
|
|
554
|
+
"",
|
|
555
|
+
"Available checks:",
|
|
556
|
+
...Object.entries(checkup_1.CHECK_INFO).map(([id, title]) => ` ${id}: ${title}`),
|
|
557
|
+
"",
|
|
558
|
+
"Examples:",
|
|
559
|
+
" postgresai checkup postgresql://user:pass@host:5432/db",
|
|
560
|
+
" postgresai checkup postgresql://user:pass@host:5432/db --check-id A003",
|
|
561
|
+
" postgresai checkup postgresql://user:pass@host:5432/db --json",
|
|
562
|
+
" postgresai checkup postgresql://user:pass@host:5432/db --output ./reports",
|
|
563
|
+
" postgresai checkup postgresql://user:pass@host:5432/db --upload --project my_project",
|
|
564
|
+
" postgresai set-default-project my_project",
|
|
565
|
+
" postgresai checkup postgresql://user:pass@host:5432/db --upload",
|
|
566
|
+
].join("\n"))
|
|
567
|
+
.action(async (conn, opts, cmd) => {
|
|
568
|
+
if (!conn) {
|
|
569
|
+
// No args — show help like other commands do (instead of a bare error).
|
|
570
|
+
cmd.outputHelp();
|
|
571
|
+
process.exitCode = 1;
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
// Preflight: validate/create output directory BEFORE connecting / running checks.
|
|
575
|
+
// This avoids waiting on network/DB work only to fail at the very end.
|
|
576
|
+
let outputPath;
|
|
577
|
+
if (opts.output && !opts.json) {
|
|
578
|
+
const outputDir = expandHomePath(opts.output);
|
|
579
|
+
outputPath = path.isAbsolute(outputDir) ? outputDir : path.resolve(process.cwd(), outputDir);
|
|
580
|
+
if (!fs.existsSync(outputPath)) {
|
|
581
|
+
try {
|
|
582
|
+
fs.mkdirSync(outputPath, { recursive: true });
|
|
583
|
+
}
|
|
584
|
+
catch (e) {
|
|
585
|
+
const errAny = e;
|
|
586
|
+
const code = typeof errAny?.code === "string" ? errAny.code : "";
|
|
587
|
+
const msg = errAny instanceof Error ? errAny.message : String(errAny);
|
|
588
|
+
if (code === "EACCES" || code === "EPERM" || code === "ENOENT") {
|
|
589
|
+
console.error(`Error: Failed to create output directory: ${outputPath}`);
|
|
590
|
+
console.error(`Reason: ${msg}`);
|
|
591
|
+
console.error("Tip: choose a writable path, e.g. --output ./reports or --output ~/reports");
|
|
592
|
+
process.exitCode = 1;
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
throw e;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
// Preflight: validate upload flags/credentials BEFORE connecting / running checks.
|
|
600
|
+
// This allows "fast-fail" for missing API key / project name.
|
|
601
|
+
let uploadCfg;
|
|
602
|
+
if (opts.upload) {
|
|
603
|
+
const rootOpts = program.opts();
|
|
604
|
+
const { apiKey } = getConfig(rootOpts);
|
|
605
|
+
if (!apiKey) {
|
|
606
|
+
console.error("Error: API key is required for --upload");
|
|
607
|
+
console.error("Tip: run 'postgresai auth' or pass --api-key / set PGAI_API_KEY");
|
|
608
|
+
process.exitCode = 1;
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
const cfg = config.readConfig();
|
|
612
|
+
const { apiBaseUrl } = (0, util_2.resolveBaseUrls)(rootOpts, cfg);
|
|
613
|
+
const project = ((opts.project || cfg.defaultProject) || "").trim();
|
|
614
|
+
if (!project) {
|
|
615
|
+
console.error("Error: --project is required (or set a default via 'postgresai set-default-project <project>')");
|
|
616
|
+
process.exitCode = 1;
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
const epoch = Math.floor(Date.now() / 1000);
|
|
620
|
+
uploadCfg = {
|
|
621
|
+
apiKey,
|
|
622
|
+
apiBaseUrl,
|
|
623
|
+
project,
|
|
624
|
+
epoch,
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
// Use the same SSL behavior as prepare-db:
|
|
628
|
+
// - Default: sslmode=prefer (try SSL first, fallback to non-SSL)
|
|
629
|
+
// - Respect PGSSLMODE env and ?sslmode=... in connection URI
|
|
630
|
+
const adminConn = (0, init_1.resolveAdminConnection)({
|
|
631
|
+
conn,
|
|
632
|
+
envPassword: process.env.PGPASSWORD,
|
|
633
|
+
});
|
|
634
|
+
let client;
|
|
635
|
+
const spinnerEnabled = !!process.stdout.isTTY && !opts.json;
|
|
636
|
+
const spinner = createTtySpinner(spinnerEnabled, "Connecting to Postgres");
|
|
637
|
+
try {
|
|
638
|
+
spinner.update("Connecting to Postgres");
|
|
639
|
+
const connResult = await (0, init_1.connectWithSslFallback)(pg_1.Client, adminConn);
|
|
640
|
+
client = connResult.client;
|
|
641
|
+
let reports;
|
|
642
|
+
let uploadSummary;
|
|
643
|
+
if (opts.checkId === "ALL") {
|
|
644
|
+
reports = await (0, checkup_1.generateAllReports)(client, opts.nodeName, (p) => {
|
|
645
|
+
spinner.update(`Running ${p.checkId}: ${p.checkTitle} (${p.index}/${p.total})`);
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
else {
|
|
649
|
+
const checkId = opts.checkId.toUpperCase();
|
|
650
|
+
const generator = checkup_1.REPORT_GENERATORS[checkId];
|
|
651
|
+
if (!generator) {
|
|
652
|
+
spinner.stop();
|
|
653
|
+
console.error(`Unknown check ID: ${opts.checkId}`);
|
|
654
|
+
console.error(`Available: ${Object.keys(checkup_1.CHECK_INFO).join(", ")}, ALL`);
|
|
655
|
+
process.exitCode = 1;
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
spinner.update(`Running ${checkId}: ${checkup_1.CHECK_INFO[checkId] || checkId}`);
|
|
659
|
+
reports = { [checkId]: await generator(client, opts.nodeName) };
|
|
660
|
+
}
|
|
661
|
+
// Optional: upload to PostgresAI API.
|
|
662
|
+
if (uploadCfg) {
|
|
663
|
+
spinner.update("Creating remote checkup report");
|
|
664
|
+
const created = await (0, checkup_api_1.createCheckupReport)({
|
|
665
|
+
apiKey: uploadCfg.apiKey,
|
|
666
|
+
apiBaseUrl: uploadCfg.apiBaseUrl,
|
|
667
|
+
project: uploadCfg.project,
|
|
668
|
+
epoch: uploadCfg.epoch,
|
|
669
|
+
});
|
|
670
|
+
const reportId = created.reportId;
|
|
671
|
+
// Keep upload progress out of stdout when --json is used.
|
|
672
|
+
const logUpload = (msg) => {
|
|
673
|
+
if (opts.json)
|
|
674
|
+
console.error(msg);
|
|
675
|
+
};
|
|
676
|
+
logUpload(`Created remote checkup report: ${reportId}`);
|
|
677
|
+
const uploaded = [];
|
|
678
|
+
for (const [checkId, report] of Object.entries(reports)) {
|
|
679
|
+
spinner.update(`Uploading ${checkId}.json`);
|
|
680
|
+
const jsonText = JSON.stringify(report, null, 2);
|
|
681
|
+
const r = await (0, checkup_api_1.uploadCheckupReportJson)({
|
|
682
|
+
apiKey: uploadCfg.apiKey,
|
|
683
|
+
apiBaseUrl: uploadCfg.apiBaseUrl,
|
|
684
|
+
reportId,
|
|
685
|
+
filename: `${checkId}.json`,
|
|
686
|
+
checkId,
|
|
687
|
+
jsonText,
|
|
688
|
+
});
|
|
689
|
+
uploaded.push({ checkId, filename: `${checkId}.json`, chunkId: r.reportChunkId });
|
|
690
|
+
}
|
|
691
|
+
logUpload("Upload completed");
|
|
692
|
+
uploadSummary = { project: uploadCfg.project, reportId, uploaded };
|
|
693
|
+
}
|
|
694
|
+
spinner.stop();
|
|
695
|
+
// Output results
|
|
696
|
+
if (opts.json) {
|
|
697
|
+
console.log(JSON.stringify(reports, null, 2));
|
|
698
|
+
}
|
|
699
|
+
else if (opts.output) {
|
|
700
|
+
// Write to files
|
|
701
|
+
// outputPath is preflight-validated above
|
|
702
|
+
const outDir = outputPath || path.resolve(process.cwd(), expandHomePath(opts.output));
|
|
703
|
+
for (const [checkId, report] of Object.entries(reports)) {
|
|
704
|
+
const filePath = path.join(outDir, `${checkId}.json`);
|
|
705
|
+
fs.writeFileSync(filePath, JSON.stringify(report, null, 2), "utf8");
|
|
706
|
+
console.log(`✓ ${checkId}: ${filePath}`);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
else if (uploadSummary) {
|
|
710
|
+
// Default with --upload: show upload result instead of local-only summary.
|
|
711
|
+
console.log("\nCheckup report uploaded");
|
|
712
|
+
console.log("======================\n");
|
|
713
|
+
console.log(`Project: ${uploadSummary.project}`);
|
|
714
|
+
console.log(`Report ID: ${uploadSummary.reportId}`);
|
|
715
|
+
console.log("View in Console: console.postgres.ai → Support → checkup reports");
|
|
716
|
+
console.log("");
|
|
717
|
+
console.log("Files:");
|
|
718
|
+
for (const item of uploadSummary.uploaded) {
|
|
719
|
+
console.log(`- ${item.checkId}: ${item.filename}`);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
else {
|
|
723
|
+
// Default: print summary
|
|
724
|
+
console.log("\nHealth Check Reports Generated:");
|
|
725
|
+
console.log("================================\n");
|
|
726
|
+
for (const [checkId, report] of Object.entries(reports)) {
|
|
727
|
+
const r = report;
|
|
728
|
+
console.log(`${checkId}: ${r.checkTitle}`);
|
|
729
|
+
if (r.results && r.results[opts.nodeName]) {
|
|
730
|
+
const nodeData = r.results[opts.nodeName];
|
|
731
|
+
if (nodeData.postgres_version) {
|
|
732
|
+
console.log(` PostgreSQL: ${nodeData.postgres_version.version}`);
|
|
733
|
+
}
|
|
734
|
+
if (checkId === "A007" && nodeData.data) {
|
|
735
|
+
const count = Object.keys(nodeData.data).length;
|
|
736
|
+
console.log(` Altered settings: ${count}`);
|
|
737
|
+
}
|
|
738
|
+
if (checkId === "A004" && nodeData.data) {
|
|
739
|
+
if (nodeData.data.database_sizes) {
|
|
740
|
+
const dbCount = Object.keys(nodeData.data.database_sizes).length;
|
|
741
|
+
console.log(` Databases: ${dbCount}`);
|
|
742
|
+
}
|
|
743
|
+
if (nodeData.data.general_info?.cache_hit_ratio) {
|
|
744
|
+
console.log(` Cache hit ratio: ${nodeData.data.general_info.cache_hit_ratio.value}%`);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
console.log("\nUse --json for full output or --output <dir> to save files");
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
catch (error) {
|
|
753
|
+
spinner.stop();
|
|
754
|
+
if (error instanceof checkup_api_1.RpcError) {
|
|
755
|
+
for (const line of (0, checkup_api_1.formatRpcErrorForDisplay)(error)) {
|
|
756
|
+
console.error(line);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
else {
|
|
760
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
761
|
+
console.error(`Error: ${message}`);
|
|
762
|
+
}
|
|
763
|
+
process.exitCode = 1;
|
|
764
|
+
}
|
|
765
|
+
finally {
|
|
766
|
+
if (client) {
|
|
767
|
+
await client.end();
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
});
|
|
478
771
|
/**
|
|
479
772
|
* Stub function for not implemented commands
|
|
480
773
|
*/
|
|
@@ -609,8 +902,8 @@ program.command("help", { isDefault: true }).description("show help").action(()
|
|
|
609
902
|
// Monitoring services management
|
|
610
903
|
const mon = program.command("mon").description("monitoring services management");
|
|
611
904
|
mon
|
|
612
|
-
.command("
|
|
613
|
-
.description("
|
|
905
|
+
.command("local-install")
|
|
906
|
+
.description("install local monitoring stack (generate config, start services)")
|
|
614
907
|
.option("--demo", "demo mode with sample database", false)
|
|
615
908
|
.option("--api-key <key>", "Postgres AI API key for automated report uploads")
|
|
616
909
|
.option("--db-url <url>", "PostgreSQL connection URL to monitor")
|
|
@@ -618,7 +911,7 @@ mon
|
|
|
618
911
|
.option("-y, --yes", "accept all defaults and skip interactive prompts", false)
|
|
619
912
|
.action(async (opts) => {
|
|
620
913
|
console.log("\n=================================");
|
|
621
|
-
console.log(" PostgresAI
|
|
914
|
+
console.log(" PostgresAI monitoring local install");
|
|
622
915
|
console.log("=================================\n");
|
|
623
916
|
console.log("This will install, configure, and start the monitoring system\n");
|
|
624
917
|
// Ensure we have a project directory with docker-compose.yml even if running from elsewhere
|
|
@@ -650,8 +943,8 @@ mon
|
|
|
650
943
|
if (opts.demo && opts.apiKey) {
|
|
651
944
|
console.error("✗ Cannot use --api-key with --demo mode");
|
|
652
945
|
console.error("✗ Demo mode is for testing only and does not support API key integration");
|
|
653
|
-
console.error("\nUse demo mode without API key: postgres-ai mon
|
|
654
|
-
console.error("Or use production mode with API key: postgres-ai mon
|
|
946
|
+
console.error("\nUse demo mode without API key: postgres-ai mon local-install --demo");
|
|
947
|
+
console.error("Or use production mode with API key: postgres-ai mon local-install --api-key=your_key");
|
|
655
948
|
process.exitCode = 1;
|
|
656
949
|
return;
|
|
657
950
|
}
|
|
@@ -893,7 +1186,7 @@ mon
|
|
|
893
1186
|
console.log("✓ Services started\n");
|
|
894
1187
|
// Final summary
|
|
895
1188
|
console.log("=================================");
|
|
896
|
-
console.log("
|
|
1189
|
+
console.log(" Local install completed!");
|
|
897
1190
|
console.log("=================================\n");
|
|
898
1191
|
console.log("What's running:");
|
|
899
1192
|
if (opts.demo) {
|
|
@@ -1409,10 +1702,24 @@ targets
|
|
|
1409
1702
|
// Authentication and API key management
|
|
1410
1703
|
program
|
|
1411
1704
|
.command("auth")
|
|
1412
|
-
.description("authenticate via browser
|
|
1705
|
+
.description("authenticate via browser (OAuth) or store API key directly")
|
|
1706
|
+
.option("--set-key <key>", "store API key directly without OAuth flow")
|
|
1413
1707
|
.option("--port <port>", "local callback server port (default: random)", parseInt)
|
|
1414
1708
|
.option("--debug", "enable debug output")
|
|
1415
1709
|
.action(async (opts) => {
|
|
1710
|
+
// If --set-key is provided, store it directly without OAuth
|
|
1711
|
+
if (opts.setKey) {
|
|
1712
|
+
const trimmedKey = opts.setKey.trim();
|
|
1713
|
+
if (!trimmedKey) {
|
|
1714
|
+
console.error("Error: API key cannot be empty");
|
|
1715
|
+
process.exitCode = 1;
|
|
1716
|
+
return;
|
|
1717
|
+
}
|
|
1718
|
+
config.writeConfig({ apiKey: trimmedKey });
|
|
1719
|
+
console.log(`API key saved to ${config.getConfigPath()}`);
|
|
1720
|
+
return;
|
|
1721
|
+
}
|
|
1722
|
+
// Otherwise, proceed with OAuth flow
|
|
1416
1723
|
const pkce = require("../lib/pkce");
|
|
1417
1724
|
const authServer = require("../lib/auth-server");
|
|
1418
1725
|
console.log("Starting authentication flow...\n");
|
|
@@ -1599,8 +1906,9 @@ program
|
|
|
1599
1906
|
});
|
|
1600
1907
|
program
|
|
1601
1908
|
.command("add-key <apiKey>")
|
|
1602
|
-
.description("store API key")
|
|
1909
|
+
.description("store API key (deprecated: use 'auth --set-key' instead)")
|
|
1603
1910
|
.action(async (apiKey) => {
|
|
1911
|
+
console.warn("Warning: 'add-key' is deprecated. Use 'auth --set-key <key>' instead.\n");
|
|
1604
1912
|
config.writeConfig({ apiKey });
|
|
1605
1913
|
console.log(`API key saved to ${config.getConfigPath()}`);
|
|
1606
1914
|
});
|
|
@@ -1717,7 +2025,7 @@ mon
|
|
|
1717
2025
|
const { projectDir } = await resolveOrInitPaths();
|
|
1718
2026
|
const cfgPath = path.resolve(projectDir, ".pgwatch-config");
|
|
1719
2027
|
if (!fs.existsSync(cfgPath)) {
|
|
1720
|
-
console.error("Configuration file not found. Run 'postgres-ai mon
|
|
2028
|
+
console.error("Configuration file not found. Run 'postgres-ai mon local-install' first.");
|
|
1721
2029
|
process.exitCode = 1;
|
|
1722
2030
|
return;
|
|
1723
2031
|
}
|