postgresai 0.11.0-alpha.9 → 0.12.0-alpha.13
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 +68 -15
- package/bin/postgres-ai.ts +194 -102
- package/dist/bin/postgres-ai.js +177 -102
- package/dist/bin/postgres-ai.js.map +1 -1
- package/dist/lib/auth-server.js +8 -8
- package/dist/lib/issues.d.ts +7 -0
- package/dist/lib/issues.d.ts.map +1 -0
- package/dist/lib/issues.js +105 -0
- package/dist/lib/issues.js.map +1 -0
- package/dist/lib/mcp-server.d.ts +9 -0
- package/dist/lib/mcp-server.d.ts.map +1 -0
- package/dist/lib/mcp-server.js +114 -0
- package/dist/lib/mcp-server.js.map +1 -0
- package/dist/lib/util.d.ts +27 -0
- package/dist/lib/util.d.ts.map +1 -0
- package/dist/lib/util.js +46 -0
- package/dist/lib/util.js.map +1 -0
- package/dist/package.json +3 -2
- package/lib/auth-server.ts +8 -8
- package/lib/issues.ts +83 -0
- package/lib/mcp-server.ts +98 -0
- package/lib/util.ts +60 -0
- package/package.json +3 -4
- package/tsconfig.json +2 -2
package/bin/postgres-ai.ts
CHANGED
|
@@ -12,6 +12,9 @@ import { promisify } from "util";
|
|
|
12
12
|
import * as readline from "readline";
|
|
13
13
|
import * as http from "https";
|
|
14
14
|
import { URL } from "url";
|
|
15
|
+
import { startMcpServer } from "../lib/mcp-server";
|
|
16
|
+
import { fetchIssues } from "../lib/issues";
|
|
17
|
+
import { resolveBaseUrls } from "../lib/util";
|
|
15
18
|
|
|
16
19
|
const execPromise = promisify(exec);
|
|
17
20
|
const execFilePromise = promisify(execFile);
|
|
@@ -154,10 +157,12 @@ program.command("help", { isDefault: true }).description("show help").action(()
|
|
|
154
157
|
program.outputHelp();
|
|
155
158
|
});
|
|
156
159
|
|
|
157
|
-
//
|
|
158
|
-
program
|
|
160
|
+
// Monitoring services management
|
|
161
|
+
const mon = program.command("mon").description("monitoring services management");
|
|
162
|
+
|
|
163
|
+
mon
|
|
159
164
|
.command("quickstart")
|
|
160
|
-
.description("complete setup (generate config, start services)")
|
|
165
|
+
.description("complete setup (generate config, start monitoring services)")
|
|
161
166
|
.option("--demo", "demo mode", false)
|
|
162
167
|
.action(async () => {
|
|
163
168
|
const code1 = await runCompose(["run", "--rm", "sources-generator"]);
|
|
@@ -168,47 +173,46 @@ program
|
|
|
168
173
|
const code2 = await runCompose(["up", "-d"]);
|
|
169
174
|
if (code2 !== 0) process.exitCode = code2;
|
|
170
175
|
});
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
.description("prepare project (no-op in repo checkout)")
|
|
174
|
-
.action(async () => {
|
|
175
|
-
console.log("Project files present; nothing to install.");
|
|
176
|
-
});
|
|
177
|
-
program
|
|
176
|
+
|
|
177
|
+
mon
|
|
178
178
|
.command("start")
|
|
179
|
-
.description("start services")
|
|
179
|
+
.description("start monitoring services")
|
|
180
180
|
.action(async () => {
|
|
181
181
|
const code = await runCompose(["up", "-d"]);
|
|
182
182
|
if (code !== 0) process.exitCode = code;
|
|
183
183
|
});
|
|
184
|
-
|
|
184
|
+
|
|
185
|
+
mon
|
|
185
186
|
.command("stop")
|
|
186
|
-
.description("stop services")
|
|
187
|
+
.description("stop monitoring services")
|
|
187
188
|
.action(async () => {
|
|
188
189
|
const code = await runCompose(["down"]);
|
|
189
190
|
if (code !== 0) process.exitCode = code;
|
|
190
191
|
});
|
|
191
|
-
|
|
192
|
+
|
|
193
|
+
mon
|
|
192
194
|
.command("restart [service]")
|
|
193
|
-
.description("restart all services or specific service")
|
|
195
|
+
.description("restart all monitoring services or specific service")
|
|
194
196
|
.action(async (service?: string) => {
|
|
195
197
|
const args = ["restart"];
|
|
196
198
|
if (service) args.push(service);
|
|
197
199
|
const code = await runCompose(args);
|
|
198
200
|
if (code !== 0) process.exitCode = code;
|
|
199
201
|
});
|
|
200
|
-
|
|
202
|
+
|
|
203
|
+
mon
|
|
201
204
|
.command("status")
|
|
202
|
-
.description("show
|
|
205
|
+
.description("show monitoring services status")
|
|
203
206
|
.action(async () => {
|
|
204
207
|
const code = await runCompose(["ps"]);
|
|
205
208
|
if (code !== 0) process.exitCode = code;
|
|
206
209
|
});
|
|
207
|
-
|
|
210
|
+
|
|
211
|
+
mon
|
|
208
212
|
.command("logs [service]")
|
|
209
213
|
.option("-f, --follow", "follow logs", false)
|
|
210
214
|
.option("--tail <lines>", "number of lines to show from the end of logs", "all")
|
|
211
|
-
.description("show logs for all or specific service")
|
|
215
|
+
.description("show logs for all or specific monitoring service")
|
|
212
216
|
.action(async (service: string | undefined, opts: { follow: boolean; tail: string }) => {
|
|
213
217
|
const args: string[] = ["logs"];
|
|
214
218
|
if (opts.follow) args.push("-f");
|
|
@@ -217,9 +221,9 @@ program
|
|
|
217
221
|
const code = await runCompose(args);
|
|
218
222
|
if (code !== 0) process.exitCode = code;
|
|
219
223
|
});
|
|
220
|
-
|
|
224
|
+
mon
|
|
221
225
|
.command("health")
|
|
222
|
-
.description("health check")
|
|
226
|
+
.description("health check for monitoring services")
|
|
223
227
|
.option("--wait <seconds>", "wait time in seconds for services to become healthy", parseInt, 0)
|
|
224
228
|
.action(async (opts: { wait: number }) => {
|
|
225
229
|
const services: HealthService[] = [
|
|
@@ -274,9 +278,9 @@ program
|
|
|
274
278
|
process.exitCode = 1;
|
|
275
279
|
}
|
|
276
280
|
});
|
|
277
|
-
|
|
281
|
+
mon
|
|
278
282
|
.command("config")
|
|
279
|
-
.description("show configuration")
|
|
283
|
+
.description("show monitoring services configuration")
|
|
280
284
|
.action(async () => {
|
|
281
285
|
const { fs, projectDir, composeFile, instancesFile } = resolvePaths();
|
|
282
286
|
console.log(`Project Directory: ${projectDir}`);
|
|
@@ -289,16 +293,16 @@ program
|
|
|
289
293
|
if (!/\n$/.test(text)) console.log();
|
|
290
294
|
}
|
|
291
295
|
});
|
|
292
|
-
|
|
296
|
+
mon
|
|
293
297
|
.command("update-config")
|
|
294
|
-
.description("apply configuration (generate sources)")
|
|
298
|
+
.description("apply monitoring services configuration (generate sources)")
|
|
295
299
|
.action(async () => {
|
|
296
300
|
const code = await runCompose(["run", "--rm", "sources-generator"]);
|
|
297
301
|
if (code !== 0) process.exitCode = code;
|
|
298
302
|
});
|
|
299
|
-
|
|
303
|
+
mon
|
|
300
304
|
.command("update")
|
|
301
|
-
.description("update
|
|
305
|
+
.description("update monitoring stack")
|
|
302
306
|
.action(async () => {
|
|
303
307
|
console.log("Updating PostgresAI monitoring stack...\n");
|
|
304
308
|
|
|
@@ -331,8 +335,8 @@ program
|
|
|
331
335
|
|
|
332
336
|
if (code === 0) {
|
|
333
337
|
console.log("\n✓ Update completed successfully");
|
|
334
|
-
console.log("\nTo apply updates, restart services:");
|
|
335
|
-
console.log(" postgres-ai restart");
|
|
338
|
+
console.log("\nTo apply updates, restart monitoring services:");
|
|
339
|
+
console.log(" postgres-ai mon restart");
|
|
336
340
|
} else {
|
|
337
341
|
console.error("\n✗ Docker image update failed");
|
|
338
342
|
process.exitCode = 1;
|
|
@@ -343,9 +347,9 @@ program
|
|
|
343
347
|
process.exitCode = 1;
|
|
344
348
|
}
|
|
345
349
|
});
|
|
346
|
-
|
|
350
|
+
mon
|
|
347
351
|
.command("reset [service]")
|
|
348
|
-
.description("reset all or specific service")
|
|
352
|
+
.description("reset all or specific monitoring service")
|
|
349
353
|
.action(async (service?: string) => {
|
|
350
354
|
const rl = readline.createInterface({
|
|
351
355
|
input: process.stdin,
|
|
@@ -414,9 +418,9 @@ program
|
|
|
414
418
|
process.exitCode = 1;
|
|
415
419
|
}
|
|
416
420
|
});
|
|
417
|
-
|
|
421
|
+
mon
|
|
418
422
|
.command("clean")
|
|
419
|
-
.description("cleanup artifacts")
|
|
423
|
+
.description("cleanup monitoring services artifacts")
|
|
420
424
|
.action(async () => {
|
|
421
425
|
console.log("Cleaning up Docker resources...\n");
|
|
422
426
|
|
|
@@ -450,25 +454,27 @@ program
|
|
|
450
454
|
process.exitCode = 1;
|
|
451
455
|
}
|
|
452
456
|
});
|
|
453
|
-
|
|
457
|
+
mon
|
|
454
458
|
.command("shell <service>")
|
|
455
|
-
.description("open service
|
|
459
|
+
.description("open shell to monitoring service")
|
|
456
460
|
.action(async (service: string) => {
|
|
457
461
|
const code = await runCompose(["exec", service, "/bin/sh"]);
|
|
458
462
|
if (code !== 0) process.exitCode = code;
|
|
459
463
|
});
|
|
460
|
-
|
|
464
|
+
mon
|
|
461
465
|
.command("check")
|
|
462
|
-
.description("system readiness check")
|
|
466
|
+
.description("monitoring services system readiness check")
|
|
463
467
|
.action(async () => {
|
|
464
468
|
const code = await runCompose(["ps"]);
|
|
465
469
|
if (code !== 0) process.exitCode = code;
|
|
466
470
|
});
|
|
467
471
|
|
|
468
|
-
//
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
+
// Monitoring targets (databases to monitor)
|
|
473
|
+
const targets = mon.command("targets").description("manage databases to monitor");
|
|
474
|
+
|
|
475
|
+
targets
|
|
476
|
+
.command("list")
|
|
477
|
+
.description("list monitoring target databases")
|
|
472
478
|
.action(async () => {
|
|
473
479
|
const instancesPath = path.resolve(process.cwd(), "instances.yml");
|
|
474
480
|
if (!fs.existsSync(instancesPath)) {
|
|
@@ -482,13 +488,13 @@ program
|
|
|
482
488
|
const instances = yaml.load(content) as Instance[] | null;
|
|
483
489
|
|
|
484
490
|
if (!instances || !Array.isArray(instances) || instances.length === 0) {
|
|
485
|
-
console.log("No
|
|
491
|
+
console.log("No monitoring targets configured");
|
|
486
492
|
console.log("");
|
|
487
|
-
console.log("To add
|
|
488
|
-
console.log(" postgres-ai add
|
|
493
|
+
console.log("To add a monitoring target:");
|
|
494
|
+
console.log(" postgres-ai mon targets add <connection-string> <name>");
|
|
489
495
|
console.log("");
|
|
490
496
|
console.log("Example:");
|
|
491
|
-
console.log(" postgres-ai add
|
|
497
|
+
console.log(" postgres-ai mon targets add 'postgresql://user:pass@host:5432/db' my-db");
|
|
492
498
|
return;
|
|
493
499
|
}
|
|
494
500
|
|
|
@@ -496,18 +502,18 @@ program
|
|
|
496
502
|
const filtered = instances.filter((inst) => inst.name && inst.name !== "target-database");
|
|
497
503
|
|
|
498
504
|
if (filtered.length === 0) {
|
|
499
|
-
console.log("No
|
|
505
|
+
console.log("No monitoring targets configured");
|
|
500
506
|
console.log("");
|
|
501
|
-
console.log("To add
|
|
502
|
-
console.log(" postgres-ai add
|
|
507
|
+
console.log("To add a monitoring target:");
|
|
508
|
+
console.log(" postgres-ai mon targets add <connection-string> <name>");
|
|
503
509
|
console.log("");
|
|
504
510
|
console.log("Example:");
|
|
505
|
-
console.log(" postgres-ai add
|
|
511
|
+
console.log(" postgres-ai mon targets add 'postgresql://user:pass@host:5432/db' my-db");
|
|
506
512
|
return;
|
|
507
513
|
}
|
|
508
514
|
|
|
509
515
|
for (const inst of filtered) {
|
|
510
|
-
console.log(`
|
|
516
|
+
console.log(`Target: ${inst.name}`);
|
|
511
517
|
}
|
|
512
518
|
} catch (err) {
|
|
513
519
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -515,9 +521,9 @@ program
|
|
|
515
521
|
process.exitCode = 1;
|
|
516
522
|
}
|
|
517
523
|
});
|
|
518
|
-
|
|
519
|
-
.command("add
|
|
520
|
-
.description("add
|
|
524
|
+
targets
|
|
525
|
+
.command("add [connStr] [name]")
|
|
526
|
+
.description("add monitoring target database")
|
|
521
527
|
.action(async (connStr?: string, name?: string) => {
|
|
522
528
|
const file = path.resolve(process.cwd(), "instances.yml");
|
|
523
529
|
if (!connStr) {
|
|
@@ -543,7 +549,7 @@ program
|
|
|
543
549
|
if (Array.isArray(instances)) {
|
|
544
550
|
const exists = instances.some((inst) => inst.name === instanceName);
|
|
545
551
|
if (exists) {
|
|
546
|
-
console.error(`
|
|
552
|
+
console.error(`Monitoring target '${instanceName}' already exists`);
|
|
547
553
|
process.exitCode = 1;
|
|
548
554
|
return;
|
|
549
555
|
}
|
|
@@ -553,7 +559,7 @@ program
|
|
|
553
559
|
// If YAML parsing fails, fall back to simple check
|
|
554
560
|
const content = fs.existsSync(file) ? fs.readFileSync(file, "utf8") : "";
|
|
555
561
|
if (new RegExp(`^- name: ${instanceName}$`, "m").test(content)) {
|
|
556
|
-
console.error(`
|
|
562
|
+
console.error(`Monitoring target '${instanceName}' already exists`);
|
|
557
563
|
process.exitCode = 1;
|
|
558
564
|
return;
|
|
559
565
|
}
|
|
@@ -563,11 +569,11 @@ program
|
|
|
563
569
|
const body = `- name: ${instanceName}\n conn_str: ${connStr}\n preset_metrics: full\n custom_metrics:\n is_enabled: true\n group: default\n custom_tags:\n env: production\n cluster: default\n node_name: ${instanceName}\n sink_type: ~sink_type~\n`;
|
|
564
570
|
const content = fs.existsSync(file) ? fs.readFileSync(file, "utf8") : "";
|
|
565
571
|
fs.appendFileSync(file, (content && !/\n$/.test(content) ? "\n" : "") + body, "utf8");
|
|
566
|
-
console.log(`
|
|
572
|
+
console.log(`Monitoring target '${instanceName}' added`);
|
|
567
573
|
});
|
|
568
|
-
|
|
569
|
-
.command("remove
|
|
570
|
-
.description("remove
|
|
574
|
+
targets
|
|
575
|
+
.command("remove <name>")
|
|
576
|
+
.description("remove monitoring target database")
|
|
571
577
|
.action(async (name: string) => {
|
|
572
578
|
const file = path.resolve(process.cwd(), "instances.yml");
|
|
573
579
|
if (!fs.existsSync(file)) {
|
|
@@ -589,22 +595,22 @@ program
|
|
|
589
595
|
const filtered = instances.filter((inst) => inst.name !== name);
|
|
590
596
|
|
|
591
597
|
if (filtered.length === instances.length) {
|
|
592
|
-
console.error(`
|
|
598
|
+
console.error(`Monitoring target '${name}' not found`);
|
|
593
599
|
process.exitCode = 1;
|
|
594
600
|
return;
|
|
595
601
|
}
|
|
596
602
|
|
|
597
603
|
fs.writeFileSync(file, yaml.dump(filtered), "utf8");
|
|
598
|
-
console.log(`
|
|
604
|
+
console.log(`Monitoring target '${name}' removed`);
|
|
599
605
|
} catch (err) {
|
|
600
606
|
const message = err instanceof Error ? err.message : String(err);
|
|
601
607
|
console.error(`Error processing instances.yml: ${message}`);
|
|
602
608
|
process.exitCode = 1;
|
|
603
609
|
}
|
|
604
610
|
});
|
|
605
|
-
|
|
606
|
-
.command("test
|
|
607
|
-
.description("test
|
|
611
|
+
targets
|
|
612
|
+
.command("test <name>")
|
|
613
|
+
.description("test monitoring target database connectivity")
|
|
608
614
|
.action(async (name: string) => {
|
|
609
615
|
const instancesPath = path.resolve(process.cwd(), "instances.yml");
|
|
610
616
|
if (!fs.existsSync(instancesPath)) {
|
|
@@ -626,18 +632,18 @@ program
|
|
|
626
632
|
const instance = instances.find((inst) => inst.name === name);
|
|
627
633
|
|
|
628
634
|
if (!instance) {
|
|
629
|
-
console.error(`
|
|
635
|
+
console.error(`Monitoring target '${name}' not found`);
|
|
630
636
|
process.exitCode = 1;
|
|
631
637
|
return;
|
|
632
638
|
}
|
|
633
639
|
|
|
634
640
|
if (!instance.conn_str) {
|
|
635
|
-
console.error(`Connection string not found for
|
|
641
|
+
console.error(`Connection string not found for monitoring target '${name}'`);
|
|
636
642
|
process.exitCode = 1;
|
|
637
643
|
return;
|
|
638
644
|
}
|
|
639
645
|
|
|
640
|
-
console.log(`Testing connection to '${name}'...`);
|
|
646
|
+
console.log(`Testing connection to monitoring target '${name}'...`);
|
|
641
647
|
|
|
642
648
|
const { stdout, stderr } = await execFilePromise(
|
|
643
649
|
"psql",
|
|
@@ -669,9 +675,8 @@ program
|
|
|
669
675
|
const params = pkce.generatePKCEParams();
|
|
670
676
|
|
|
671
677
|
const rootOpts = program.opts<CliOptions>();
|
|
672
|
-
|
|
673
|
-
const
|
|
674
|
-
const uiBaseUrl = (rootOpts.uiBaseUrl || process.env.PGAI_UI_BASE_URL || "https://console.postgres.ai").replace(/\/$/, "");
|
|
678
|
+
const cfg = config.readConfig();
|
|
679
|
+
const { apiBaseUrl, uiBaseUrl } = resolveBaseUrls(rootOpts, cfg);
|
|
675
680
|
|
|
676
681
|
if (opts.debug) {
|
|
677
682
|
console.log(`Debug: Resolved API base URL: ${apiBaseUrl}`);
|
|
@@ -682,7 +687,7 @@ program
|
|
|
682
687
|
// Step 1: Start local callback server FIRST to get actual port
|
|
683
688
|
console.log("Starting local callback server...");
|
|
684
689
|
const requestedPort = opts.port || 0; // 0 = OS assigns available port
|
|
685
|
-
const callbackServer = authServer.createCallbackServer(requestedPort, params.state,
|
|
690
|
+
const callbackServer = authServer.createCallbackServer(requestedPort, params.state, 120000); // 2 minute timeout
|
|
686
691
|
|
|
687
692
|
// Wait a bit for server to start and get port
|
|
688
693
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
@@ -724,10 +729,20 @@ program
|
|
|
724
729
|
res.on("end", async () => {
|
|
725
730
|
if (res.statusCode !== 200) {
|
|
726
731
|
console.error(`Failed to initialize auth session: ${res.statusCode}`);
|
|
727
|
-
|
|
732
|
+
|
|
733
|
+
// Check if response is HTML (common for 404 pages)
|
|
734
|
+
if (data.trim().startsWith("<!") || data.trim().startsWith("<html")) {
|
|
735
|
+
console.error("Error: Received HTML response instead of JSON. This usually means:");
|
|
736
|
+
console.error(" 1. The API endpoint URL is incorrect");
|
|
737
|
+
console.error(" 2. The endpoint does not exist (404)");
|
|
738
|
+
console.error(`\nAPI URL attempted: ${initUrl.toString()}`);
|
|
739
|
+
console.error("\nPlease verify the --api-base-url parameter.");
|
|
740
|
+
} else {
|
|
741
|
+
console.error(data);
|
|
742
|
+
}
|
|
743
|
+
|
|
728
744
|
callbackServer.server.close();
|
|
729
|
-
process.
|
|
730
|
-
return;
|
|
745
|
+
process.exit(1);
|
|
731
746
|
}
|
|
732
747
|
|
|
733
748
|
// Step 3: Open browser
|
|
@@ -748,9 +763,22 @@ program
|
|
|
748
763
|
|
|
749
764
|
// Step 4: Wait for callback
|
|
750
765
|
console.log("Waiting for authorization...");
|
|
766
|
+
console.log("(Press Ctrl+C to cancel)\n");
|
|
767
|
+
|
|
768
|
+
// Handle Ctrl+C gracefully
|
|
769
|
+
const cancelHandler = () => {
|
|
770
|
+
console.log("\n\nAuthentication cancelled by user.");
|
|
771
|
+
callbackServer.server.close();
|
|
772
|
+
process.exit(130); // Standard exit code for SIGINT
|
|
773
|
+
};
|
|
774
|
+
process.on("SIGINT", cancelHandler);
|
|
775
|
+
|
|
751
776
|
try {
|
|
752
777
|
const { code } = await callbackServer.promise;
|
|
753
778
|
|
|
779
|
+
// Remove the cancel handler after successful auth
|
|
780
|
+
process.off("SIGINT", cancelHandler);
|
|
781
|
+
|
|
754
782
|
// Step 5: Exchange code for token
|
|
755
783
|
console.log("\nExchanging authorization code for API token...");
|
|
756
784
|
const exchangeData = JSON.stringify({
|
|
@@ -758,7 +786,6 @@ program
|
|
|
758
786
|
code_verifier: params.codeVerifier,
|
|
759
787
|
state: params.state,
|
|
760
788
|
});
|
|
761
|
-
|
|
762
789
|
const exchangeUrl = new URL(`${apiBaseUrl}/rpc/oauth_token_exchange`);
|
|
763
790
|
const exchangeReq = http.request(
|
|
764
791
|
exchangeUrl,
|
|
@@ -770,20 +797,31 @@ program
|
|
|
770
797
|
},
|
|
771
798
|
},
|
|
772
799
|
(exchangeRes) => {
|
|
773
|
-
let
|
|
774
|
-
exchangeRes.on("data", (chunk) => (
|
|
800
|
+
let exchangeBody = "";
|
|
801
|
+
exchangeRes.on("data", (chunk) => (exchangeBody += chunk));
|
|
775
802
|
exchangeRes.on("end", () => {
|
|
776
803
|
if (exchangeRes.statusCode !== 200) {
|
|
777
804
|
console.error(`Failed to exchange code for token: ${exchangeRes.statusCode}`);
|
|
778
|
-
|
|
779
|
-
|
|
805
|
+
|
|
806
|
+
// Check if response is HTML (common for 404 pages)
|
|
807
|
+
if (exchangeBody.trim().startsWith("<!") || exchangeBody.trim().startsWith("<html")) {
|
|
808
|
+
console.error("Error: Received HTML response instead of JSON. This usually means:");
|
|
809
|
+
console.error(" 1. The API endpoint URL is incorrect");
|
|
810
|
+
console.error(" 2. The endpoint does not exist (404)");
|
|
811
|
+
console.error(`\nAPI URL attempted: ${exchangeUrl.toString()}`);
|
|
812
|
+
console.error("\nPlease verify the --api-base-url parameter.");
|
|
813
|
+
} else {
|
|
814
|
+
console.error(exchangeBody);
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
process.exit(1);
|
|
780
818
|
return;
|
|
781
819
|
}
|
|
782
820
|
|
|
783
821
|
try {
|
|
784
|
-
const result = JSON.parse(
|
|
785
|
-
const apiToken = result.api_token;
|
|
786
|
-
const orgId = result.org_id;
|
|
822
|
+
const result = JSON.parse(exchangeBody);
|
|
823
|
+
const apiToken = result.api_token || result?.[0]?.result?.api_token; // There is a bug with PostgREST Caching that may return an array, not single object, it's a workaround to support both cases.
|
|
824
|
+
const orgId = result.org_id || result?.[0]?.result?.org_id; // There is a bug with PostgREST Caching that may return an array, not single object, it's a workaround to support both cases.
|
|
787
825
|
|
|
788
826
|
// Step 6: Save token to config
|
|
789
827
|
config.writeConfig({
|
|
@@ -796,10 +834,11 @@ program
|
|
|
796
834
|
console.log(`API key saved to: ${config.getConfigPath()}`);
|
|
797
835
|
console.log(`Organization ID: ${orgId}`);
|
|
798
836
|
console.log(`\nYou can now use the CLI without specifying an API key.`);
|
|
837
|
+
process.exit(0);
|
|
799
838
|
} catch (err) {
|
|
800
839
|
const message = err instanceof Error ? err.message : String(err);
|
|
801
840
|
console.error(`Failed to parse response: ${message}`);
|
|
802
|
-
process.
|
|
841
|
+
process.exit(1);
|
|
803
842
|
}
|
|
804
843
|
});
|
|
805
844
|
}
|
|
@@ -807,16 +846,28 @@ program
|
|
|
807
846
|
|
|
808
847
|
exchangeReq.on("error", (err: Error) => {
|
|
809
848
|
console.error(`Exchange request failed: ${err.message}`);
|
|
810
|
-
process.
|
|
849
|
+
process.exit(1);
|
|
811
850
|
});
|
|
812
851
|
|
|
813
852
|
exchangeReq.write(exchangeData);
|
|
814
853
|
exchangeReq.end();
|
|
815
854
|
|
|
816
855
|
} catch (err) {
|
|
856
|
+
// Remove the cancel handler in error case too
|
|
857
|
+
process.off("SIGINT", cancelHandler);
|
|
858
|
+
|
|
817
859
|
const message = err instanceof Error ? err.message : String(err);
|
|
818
|
-
|
|
819
|
-
|
|
860
|
+
|
|
861
|
+
// Provide more helpful error messages
|
|
862
|
+
if (message.includes("timeout")) {
|
|
863
|
+
console.error(`\nAuthentication timed out.`);
|
|
864
|
+
console.error(`This usually means you closed the browser window without completing authentication.`);
|
|
865
|
+
console.error(`Please try again and complete the authentication flow.`);
|
|
866
|
+
} else {
|
|
867
|
+
console.error(`\nAuthentication failed: ${message}`);
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
process.exit(1);
|
|
820
871
|
}
|
|
821
872
|
});
|
|
822
873
|
}
|
|
@@ -825,7 +876,7 @@ program
|
|
|
825
876
|
initReq.on("error", (err: Error) => {
|
|
826
877
|
console.error(`Failed to connect to API: ${err.message}`);
|
|
827
878
|
callbackServer.server.close();
|
|
828
|
-
process.
|
|
879
|
+
process.exit(1);
|
|
829
880
|
});
|
|
830
881
|
|
|
831
882
|
initReq.write(initData);
|
|
@@ -834,7 +885,7 @@ program
|
|
|
834
885
|
} catch (err) {
|
|
835
886
|
const message = err instanceof Error ? err.message : String(err);
|
|
836
887
|
console.error(`Authentication error: ${message}`);
|
|
837
|
-
process.
|
|
888
|
+
process.exit(1);
|
|
838
889
|
}
|
|
839
890
|
});
|
|
840
891
|
|
|
@@ -856,13 +907,8 @@ program
|
|
|
856
907
|
console.log(`\nTo authenticate, run: pgai auth`);
|
|
857
908
|
return;
|
|
858
909
|
}
|
|
859
|
-
const
|
|
860
|
-
|
|
861
|
-
if (k.length <= 16) return `${k.slice(0, 4)}${"*".repeat(k.length - 8)}${k.slice(-4)}`;
|
|
862
|
-
// For longer keys, show more of the beginning to help identify them
|
|
863
|
-
return `${k.slice(0, Math.min(12, k.length - 8))}${"*".repeat(Math.max(4, k.length - 16))}${k.slice(-4)}`;
|
|
864
|
-
};
|
|
865
|
-
console.log(`Current API key: ${mask(cfg.apiKey)}`);
|
|
910
|
+
const { maskSecret } = require("../lib/util");
|
|
911
|
+
console.log(`Current API key: ${maskSecret(cfg.apiKey)}`);
|
|
866
912
|
if (cfg.orgId) {
|
|
867
913
|
console.log(`Organization ID: ${cfg.orgId}`);
|
|
868
914
|
}
|
|
@@ -908,9 +954,9 @@ program
|
|
|
908
954
|
console.log("API key removed");
|
|
909
955
|
console.log(`\nTo authenticate again, run: pgai auth`);
|
|
910
956
|
});
|
|
911
|
-
|
|
957
|
+
mon
|
|
912
958
|
.command("generate-grafana-password")
|
|
913
|
-
.description("generate Grafana password")
|
|
959
|
+
.description("generate Grafana password for monitoring services")
|
|
914
960
|
.action(async () => {
|
|
915
961
|
const cfgPath = path.resolve(process.cwd(), ".pgwatch-config");
|
|
916
962
|
|
|
@@ -950,8 +996,8 @@ program
|
|
|
950
996
|
console.log(" URL: http://localhost:3000");
|
|
951
997
|
console.log(" Username: monitor");
|
|
952
998
|
console.log(` Password: ${newPassword}`);
|
|
953
|
-
console.log("\
|
|
954
|
-
console.log(" postgres-ai
|
|
999
|
+
console.log("\nReset Grafana to apply new password:");
|
|
1000
|
+
console.log(" postgres-ai mon reset grafana");
|
|
955
1001
|
} catch (error) {
|
|
956
1002
|
const message = error instanceof Error ? error.message : String(error);
|
|
957
1003
|
console.error(`Failed to generate password: ${message}`);
|
|
@@ -959,13 +1005,13 @@ program
|
|
|
959
1005
|
process.exitCode = 1;
|
|
960
1006
|
}
|
|
961
1007
|
});
|
|
962
|
-
|
|
1008
|
+
mon
|
|
963
1009
|
.command("show-grafana-credentials")
|
|
964
|
-
.description("show Grafana credentials")
|
|
1010
|
+
.description("show Grafana credentials for monitoring services")
|
|
965
1011
|
.action(async () => {
|
|
966
1012
|
const cfgPath = path.resolve(process.cwd(), ".pgwatch-config");
|
|
967
1013
|
if (!fs.existsSync(cfgPath)) {
|
|
968
|
-
console.error("Configuration file not found. Run 'quickstart' first.");
|
|
1014
|
+
console.error("Configuration file not found. Run 'postgres-ai mon quickstart' first.");
|
|
969
1015
|
process.exitCode = 1;
|
|
970
1016
|
return;
|
|
971
1017
|
}
|
|
@@ -999,5 +1045,51 @@ program
|
|
|
999
1045
|
console.log("");
|
|
1000
1046
|
});
|
|
1001
1047
|
|
|
1048
|
+
// Issues management
|
|
1049
|
+
const issues = program.command("issues").description("issues management");
|
|
1050
|
+
|
|
1051
|
+
issues
|
|
1052
|
+
.command("list")
|
|
1053
|
+
.description("list issues")
|
|
1054
|
+
.option("--debug", "enable debug output")
|
|
1055
|
+
.action(async (opts: { debug?: boolean }) => {
|
|
1056
|
+
try {
|
|
1057
|
+
const rootOpts = program.opts<CliOptions>();
|
|
1058
|
+
const cfg = config.readConfig();
|
|
1059
|
+
const { apiKey } = getConfig(rootOpts);
|
|
1060
|
+
if (!apiKey) {
|
|
1061
|
+
console.error("API key is required. Run 'pgai auth' first or set --api-key.");
|
|
1062
|
+
process.exitCode = 1;
|
|
1063
|
+
return;
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
const { apiBaseUrl } = resolveBaseUrls(rootOpts, cfg);
|
|
1067
|
+
|
|
1068
|
+
const result = await fetchIssues({ apiKey, apiBaseUrl, debug: !!opts.debug });
|
|
1069
|
+
if (typeof result === "string") {
|
|
1070
|
+
process.stdout.write(result);
|
|
1071
|
+
if (!/\n$/.test(result)) console.log();
|
|
1072
|
+
} else {
|
|
1073
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1074
|
+
}
|
|
1075
|
+
} catch (err) {
|
|
1076
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1077
|
+
console.error(message);
|
|
1078
|
+
process.exitCode = 1;
|
|
1079
|
+
}
|
|
1080
|
+
});
|
|
1081
|
+
|
|
1082
|
+
// MCP server
|
|
1083
|
+
const mcp = program.command("mcp").description("MCP server integration");
|
|
1084
|
+
|
|
1085
|
+
mcp
|
|
1086
|
+
.command("start")
|
|
1087
|
+
.description("start MCP stdio server")
|
|
1088
|
+
.option("--debug", "enable debug output")
|
|
1089
|
+
.action(async (opts: { debug?: boolean }) => {
|
|
1090
|
+
const rootOpts = program.opts<CliOptions>();
|
|
1091
|
+
await startMcpServer(rootOpts, { debug: !!opts.debug });
|
|
1092
|
+
});
|
|
1093
|
+
|
|
1002
1094
|
program.parseAsync(process.argv);
|
|
1003
1095
|
|