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/dist/bin/postgres-ai.js
CHANGED
|
@@ -45,6 +45,9 @@ const util_1 = require("util");
|
|
|
45
45
|
const readline = __importStar(require("readline"));
|
|
46
46
|
const http = __importStar(require("https"));
|
|
47
47
|
const url_1 = require("url");
|
|
48
|
+
const mcp_server_1 = require("../lib/mcp-server");
|
|
49
|
+
const issues_1 = require("../lib/issues");
|
|
50
|
+
const util_2 = require("../lib/util");
|
|
48
51
|
const execPromise = (0, util_1.promisify)(child_process_1.exec);
|
|
49
52
|
const execFilePromise = (0, util_1.promisify)(child_process_1.execFile);
|
|
50
53
|
/**
|
|
@@ -122,10 +125,11 @@ async function runCompose(args) {
|
|
|
122
125
|
program.command("help", { isDefault: true }).description("show help").action(() => {
|
|
123
126
|
program.outputHelp();
|
|
124
127
|
});
|
|
125
|
-
//
|
|
126
|
-
program
|
|
128
|
+
// Monitoring services management
|
|
129
|
+
const mon = program.command("mon").description("monitoring services management");
|
|
130
|
+
mon
|
|
127
131
|
.command("quickstart")
|
|
128
|
-
.description("complete setup (generate config, start services)")
|
|
132
|
+
.description("complete setup (generate config, start monitoring services)")
|
|
129
133
|
.option("--demo", "demo mode", false)
|
|
130
134
|
.action(async () => {
|
|
131
135
|
const code1 = await runCompose(["run", "--rm", "sources-generator"]);
|
|
@@ -137,31 +141,25 @@ program
|
|
|
137
141
|
if (code2 !== 0)
|
|
138
142
|
process.exitCode = code2;
|
|
139
143
|
});
|
|
140
|
-
|
|
141
|
-
.command("install")
|
|
142
|
-
.description("prepare project (no-op in repo checkout)")
|
|
143
|
-
.action(async () => {
|
|
144
|
-
console.log("Project files present; nothing to install.");
|
|
145
|
-
});
|
|
146
|
-
program
|
|
144
|
+
mon
|
|
147
145
|
.command("start")
|
|
148
|
-
.description("start services")
|
|
146
|
+
.description("start monitoring services")
|
|
149
147
|
.action(async () => {
|
|
150
148
|
const code = await runCompose(["up", "-d"]);
|
|
151
149
|
if (code !== 0)
|
|
152
150
|
process.exitCode = code;
|
|
153
151
|
});
|
|
154
|
-
|
|
152
|
+
mon
|
|
155
153
|
.command("stop")
|
|
156
|
-
.description("stop services")
|
|
154
|
+
.description("stop monitoring services")
|
|
157
155
|
.action(async () => {
|
|
158
156
|
const code = await runCompose(["down"]);
|
|
159
157
|
if (code !== 0)
|
|
160
158
|
process.exitCode = code;
|
|
161
159
|
});
|
|
162
|
-
|
|
160
|
+
mon
|
|
163
161
|
.command("restart [service]")
|
|
164
|
-
.description("restart all services or specific service")
|
|
162
|
+
.description("restart all monitoring services or specific service")
|
|
165
163
|
.action(async (service) => {
|
|
166
164
|
const args = ["restart"];
|
|
167
165
|
if (service)
|
|
@@ -170,19 +168,19 @@ program
|
|
|
170
168
|
if (code !== 0)
|
|
171
169
|
process.exitCode = code;
|
|
172
170
|
});
|
|
173
|
-
|
|
171
|
+
mon
|
|
174
172
|
.command("status")
|
|
175
|
-
.description("show
|
|
173
|
+
.description("show monitoring services status")
|
|
176
174
|
.action(async () => {
|
|
177
175
|
const code = await runCompose(["ps"]);
|
|
178
176
|
if (code !== 0)
|
|
179
177
|
process.exitCode = code;
|
|
180
178
|
});
|
|
181
|
-
|
|
179
|
+
mon
|
|
182
180
|
.command("logs [service]")
|
|
183
181
|
.option("-f, --follow", "follow logs", false)
|
|
184
182
|
.option("--tail <lines>", "number of lines to show from the end of logs", "all")
|
|
185
|
-
.description("show logs for all or specific service")
|
|
183
|
+
.description("show logs for all or specific monitoring service")
|
|
186
184
|
.action(async (service, opts) => {
|
|
187
185
|
const args = ["logs"];
|
|
188
186
|
if (opts.follow)
|
|
@@ -195,9 +193,9 @@ program
|
|
|
195
193
|
if (code !== 0)
|
|
196
194
|
process.exitCode = code;
|
|
197
195
|
});
|
|
198
|
-
|
|
196
|
+
mon
|
|
199
197
|
.command("health")
|
|
200
|
-
.description("health check")
|
|
198
|
+
.description("health check for monitoring services")
|
|
201
199
|
.option("--wait <seconds>", "wait time in seconds for services to become healthy", parseInt, 0)
|
|
202
200
|
.action(async (opts) => {
|
|
203
201
|
const services = [
|
|
@@ -246,9 +244,9 @@ program
|
|
|
246
244
|
process.exitCode = 1;
|
|
247
245
|
}
|
|
248
246
|
});
|
|
249
|
-
|
|
247
|
+
mon
|
|
250
248
|
.command("config")
|
|
251
|
-
.description("show configuration")
|
|
249
|
+
.description("show monitoring services configuration")
|
|
252
250
|
.action(async () => {
|
|
253
251
|
const { fs, projectDir, composeFile, instancesFile } = resolvePaths();
|
|
254
252
|
console.log(`Project Directory: ${projectDir}`);
|
|
@@ -262,17 +260,17 @@ program
|
|
|
262
260
|
console.log();
|
|
263
261
|
}
|
|
264
262
|
});
|
|
265
|
-
|
|
263
|
+
mon
|
|
266
264
|
.command("update-config")
|
|
267
|
-
.description("apply configuration (generate sources)")
|
|
265
|
+
.description("apply monitoring services configuration (generate sources)")
|
|
268
266
|
.action(async () => {
|
|
269
267
|
const code = await runCompose(["run", "--rm", "sources-generator"]);
|
|
270
268
|
if (code !== 0)
|
|
271
269
|
process.exitCode = code;
|
|
272
270
|
});
|
|
273
|
-
|
|
271
|
+
mon
|
|
274
272
|
.command("update")
|
|
275
|
-
.description("update
|
|
273
|
+
.description("update monitoring stack")
|
|
276
274
|
.action(async () => {
|
|
277
275
|
console.log("Updating PostgresAI monitoring stack...\n");
|
|
278
276
|
try {
|
|
@@ -299,8 +297,8 @@ program
|
|
|
299
297
|
const code = await runCompose(["pull"]);
|
|
300
298
|
if (code === 0) {
|
|
301
299
|
console.log("\n✓ Update completed successfully");
|
|
302
|
-
console.log("\nTo apply updates, restart services:");
|
|
303
|
-
console.log(" postgres-ai restart");
|
|
300
|
+
console.log("\nTo apply updates, restart monitoring services:");
|
|
301
|
+
console.log(" postgres-ai mon restart");
|
|
304
302
|
}
|
|
305
303
|
else {
|
|
306
304
|
console.error("\n✗ Docker image update failed");
|
|
@@ -313,9 +311,9 @@ program
|
|
|
313
311
|
process.exitCode = 1;
|
|
314
312
|
}
|
|
315
313
|
});
|
|
316
|
-
|
|
314
|
+
mon
|
|
317
315
|
.command("reset [service]")
|
|
318
|
-
.description("reset all or specific service")
|
|
316
|
+
.description("reset all or specific monitoring service")
|
|
319
317
|
.action(async (service) => {
|
|
320
318
|
const rl = readline.createInterface({
|
|
321
319
|
input: process.stdin,
|
|
@@ -376,9 +374,9 @@ program
|
|
|
376
374
|
process.exitCode = 1;
|
|
377
375
|
}
|
|
378
376
|
});
|
|
379
|
-
|
|
377
|
+
mon
|
|
380
378
|
.command("clean")
|
|
381
|
-
.description("cleanup artifacts")
|
|
379
|
+
.description("cleanup monitoring services artifacts")
|
|
382
380
|
.action(async () => {
|
|
383
381
|
console.log("Cleaning up Docker resources...\n");
|
|
384
382
|
try {
|
|
@@ -409,26 +407,27 @@ program
|
|
|
409
407
|
process.exitCode = 1;
|
|
410
408
|
}
|
|
411
409
|
});
|
|
412
|
-
|
|
410
|
+
mon
|
|
413
411
|
.command("shell <service>")
|
|
414
|
-
.description("open service
|
|
412
|
+
.description("open shell to monitoring service")
|
|
415
413
|
.action(async (service) => {
|
|
416
414
|
const code = await runCompose(["exec", service, "/bin/sh"]);
|
|
417
415
|
if (code !== 0)
|
|
418
416
|
process.exitCode = code;
|
|
419
417
|
});
|
|
420
|
-
|
|
418
|
+
mon
|
|
421
419
|
.command("check")
|
|
422
|
-
.description("system readiness check")
|
|
420
|
+
.description("monitoring services system readiness check")
|
|
423
421
|
.action(async () => {
|
|
424
422
|
const code = await runCompose(["ps"]);
|
|
425
423
|
if (code !== 0)
|
|
426
424
|
process.exitCode = code;
|
|
427
425
|
});
|
|
428
|
-
//
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
.
|
|
426
|
+
// Monitoring targets (databases to monitor)
|
|
427
|
+
const targets = mon.command("targets").description("manage databases to monitor");
|
|
428
|
+
targets
|
|
429
|
+
.command("list")
|
|
430
|
+
.description("list monitoring target databases")
|
|
432
431
|
.action(async () => {
|
|
433
432
|
const instancesPath = path.resolve(process.cwd(), "instances.yml");
|
|
434
433
|
if (!fs.existsSync(instancesPath)) {
|
|
@@ -440,29 +439,29 @@ program
|
|
|
440
439
|
const content = fs.readFileSync(instancesPath, "utf8");
|
|
441
440
|
const instances = yaml.load(content);
|
|
442
441
|
if (!instances || !Array.isArray(instances) || instances.length === 0) {
|
|
443
|
-
console.log("No
|
|
442
|
+
console.log("No monitoring targets configured");
|
|
444
443
|
console.log("");
|
|
445
|
-
console.log("To add
|
|
446
|
-
console.log(" postgres-ai add
|
|
444
|
+
console.log("To add a monitoring target:");
|
|
445
|
+
console.log(" postgres-ai mon targets add <connection-string> <name>");
|
|
447
446
|
console.log("");
|
|
448
447
|
console.log("Example:");
|
|
449
|
-
console.log(" postgres-ai add
|
|
448
|
+
console.log(" postgres-ai mon targets add 'postgresql://user:pass@host:5432/db' my-db");
|
|
450
449
|
return;
|
|
451
450
|
}
|
|
452
451
|
// Filter out demo placeholder
|
|
453
452
|
const filtered = instances.filter((inst) => inst.name && inst.name !== "target-database");
|
|
454
453
|
if (filtered.length === 0) {
|
|
455
|
-
console.log("No
|
|
454
|
+
console.log("No monitoring targets configured");
|
|
456
455
|
console.log("");
|
|
457
|
-
console.log("To add
|
|
458
|
-
console.log(" postgres-ai add
|
|
456
|
+
console.log("To add a monitoring target:");
|
|
457
|
+
console.log(" postgres-ai mon targets add <connection-string> <name>");
|
|
459
458
|
console.log("");
|
|
460
459
|
console.log("Example:");
|
|
461
|
-
console.log(" postgres-ai add
|
|
460
|
+
console.log(" postgres-ai mon targets add 'postgresql://user:pass@host:5432/db' my-db");
|
|
462
461
|
return;
|
|
463
462
|
}
|
|
464
463
|
for (const inst of filtered) {
|
|
465
|
-
console.log(`
|
|
464
|
+
console.log(`Target: ${inst.name}`);
|
|
466
465
|
}
|
|
467
466
|
}
|
|
468
467
|
catch (err) {
|
|
@@ -471,9 +470,9 @@ program
|
|
|
471
470
|
process.exitCode = 1;
|
|
472
471
|
}
|
|
473
472
|
});
|
|
474
|
-
|
|
475
|
-
.command("add
|
|
476
|
-
.description("add
|
|
473
|
+
targets
|
|
474
|
+
.command("add [connStr] [name]")
|
|
475
|
+
.description("add monitoring target database")
|
|
477
476
|
.action(async (connStr, name) => {
|
|
478
477
|
const file = path.resolve(process.cwd(), "instances.yml");
|
|
479
478
|
if (!connStr) {
|
|
@@ -498,7 +497,7 @@ program
|
|
|
498
497
|
if (Array.isArray(instances)) {
|
|
499
498
|
const exists = instances.some((inst) => inst.name === instanceName);
|
|
500
499
|
if (exists) {
|
|
501
|
-
console.error(`
|
|
500
|
+
console.error(`Monitoring target '${instanceName}' already exists`);
|
|
502
501
|
process.exitCode = 1;
|
|
503
502
|
return;
|
|
504
503
|
}
|
|
@@ -509,7 +508,7 @@ program
|
|
|
509
508
|
// If YAML parsing fails, fall back to simple check
|
|
510
509
|
const content = fs.existsSync(file) ? fs.readFileSync(file, "utf8") : "";
|
|
511
510
|
if (new RegExp(`^- name: ${instanceName}$`, "m").test(content)) {
|
|
512
|
-
console.error(`
|
|
511
|
+
console.error(`Monitoring target '${instanceName}' already exists`);
|
|
513
512
|
process.exitCode = 1;
|
|
514
513
|
return;
|
|
515
514
|
}
|
|
@@ -518,11 +517,11 @@ program
|
|
|
518
517
|
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`;
|
|
519
518
|
const content = fs.existsSync(file) ? fs.readFileSync(file, "utf8") : "";
|
|
520
519
|
fs.appendFileSync(file, (content && !/\n$/.test(content) ? "\n" : "") + body, "utf8");
|
|
521
|
-
console.log(`
|
|
520
|
+
console.log(`Monitoring target '${instanceName}' added`);
|
|
522
521
|
});
|
|
523
|
-
|
|
524
|
-
.command("remove
|
|
525
|
-
.description("remove
|
|
522
|
+
targets
|
|
523
|
+
.command("remove <name>")
|
|
524
|
+
.description("remove monitoring target database")
|
|
526
525
|
.action(async (name) => {
|
|
527
526
|
const file = path.resolve(process.cwd(), "instances.yml");
|
|
528
527
|
if (!fs.existsSync(file)) {
|
|
@@ -540,12 +539,12 @@ program
|
|
|
540
539
|
}
|
|
541
540
|
const filtered = instances.filter((inst) => inst.name !== name);
|
|
542
541
|
if (filtered.length === instances.length) {
|
|
543
|
-
console.error(`
|
|
542
|
+
console.error(`Monitoring target '${name}' not found`);
|
|
544
543
|
process.exitCode = 1;
|
|
545
544
|
return;
|
|
546
545
|
}
|
|
547
546
|
fs.writeFileSync(file, yaml.dump(filtered), "utf8");
|
|
548
|
-
console.log(`
|
|
547
|
+
console.log(`Monitoring target '${name}' removed`);
|
|
549
548
|
}
|
|
550
549
|
catch (err) {
|
|
551
550
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -553,9 +552,9 @@ program
|
|
|
553
552
|
process.exitCode = 1;
|
|
554
553
|
}
|
|
555
554
|
});
|
|
556
|
-
|
|
557
|
-
.command("test
|
|
558
|
-
.description("test
|
|
555
|
+
targets
|
|
556
|
+
.command("test <name>")
|
|
557
|
+
.description("test monitoring target database connectivity")
|
|
559
558
|
.action(async (name) => {
|
|
560
559
|
const instancesPath = path.resolve(process.cwd(), "instances.yml");
|
|
561
560
|
if (!fs.existsSync(instancesPath)) {
|
|
@@ -573,16 +572,16 @@ program
|
|
|
573
572
|
}
|
|
574
573
|
const instance = instances.find((inst) => inst.name === name);
|
|
575
574
|
if (!instance) {
|
|
576
|
-
console.error(`
|
|
575
|
+
console.error(`Monitoring target '${name}' not found`);
|
|
577
576
|
process.exitCode = 1;
|
|
578
577
|
return;
|
|
579
578
|
}
|
|
580
579
|
if (!instance.conn_str) {
|
|
581
|
-
console.error(`Connection string not found for
|
|
580
|
+
console.error(`Connection string not found for monitoring target '${name}'`);
|
|
582
581
|
process.exitCode = 1;
|
|
583
582
|
return;
|
|
584
583
|
}
|
|
585
|
-
console.log(`Testing connection to '${name}'...`);
|
|
584
|
+
console.log(`Testing connection to monitoring target '${name}'...`);
|
|
586
585
|
const { stdout, stderr } = await execFilePromise("psql", [instance.conn_str, "-c", "SELECT version();", "--no-psqlrc"], { timeout: 10000, env: { ...process.env, PAGER: 'cat' } });
|
|
587
586
|
console.log(`✓ Connection successful`);
|
|
588
587
|
console.log(stdout.trim());
|
|
@@ -606,8 +605,8 @@ program
|
|
|
606
605
|
// Generate PKCE parameters
|
|
607
606
|
const params = pkce.generatePKCEParams();
|
|
608
607
|
const rootOpts = program.opts();
|
|
609
|
-
const
|
|
610
|
-
const
|
|
608
|
+
const cfg = config.readConfig();
|
|
609
|
+
const { apiBaseUrl, uiBaseUrl } = (0, util_2.resolveBaseUrls)(rootOpts, cfg);
|
|
611
610
|
if (opts.debug) {
|
|
612
611
|
console.log(`Debug: Resolved API base URL: ${apiBaseUrl}`);
|
|
613
612
|
console.log(`Debug: Resolved UI base URL: ${uiBaseUrl}`);
|
|
@@ -616,7 +615,7 @@ program
|
|
|
616
615
|
// Step 1: Start local callback server FIRST to get actual port
|
|
617
616
|
console.log("Starting local callback server...");
|
|
618
617
|
const requestedPort = opts.port || 0; // 0 = OS assigns available port
|
|
619
|
-
const callbackServer = authServer.createCallbackServer(requestedPort, params.state,
|
|
618
|
+
const callbackServer = authServer.createCallbackServer(requestedPort, params.state, 120000); // 2 minute timeout
|
|
620
619
|
// Wait a bit for server to start and get port
|
|
621
620
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
622
621
|
const actualPort = callbackServer.getPort();
|
|
@@ -649,10 +648,19 @@ program
|
|
|
649
648
|
res.on("end", async () => {
|
|
650
649
|
if (res.statusCode !== 200) {
|
|
651
650
|
console.error(`Failed to initialize auth session: ${res.statusCode}`);
|
|
652
|
-
|
|
651
|
+
// Check if response is HTML (common for 404 pages)
|
|
652
|
+
if (data.trim().startsWith("<!") || data.trim().startsWith("<html")) {
|
|
653
|
+
console.error("Error: Received HTML response instead of JSON. This usually means:");
|
|
654
|
+
console.error(" 1. The API endpoint URL is incorrect");
|
|
655
|
+
console.error(" 2. The endpoint does not exist (404)");
|
|
656
|
+
console.error(`\nAPI URL attempted: ${initUrl.toString()}`);
|
|
657
|
+
console.error("\nPlease verify the --api-base-url parameter.");
|
|
658
|
+
}
|
|
659
|
+
else {
|
|
660
|
+
console.error(data);
|
|
661
|
+
}
|
|
653
662
|
callbackServer.server.close();
|
|
654
|
-
process.
|
|
655
|
-
return;
|
|
663
|
+
process.exit(1);
|
|
656
664
|
}
|
|
657
665
|
// Step 3: Open browser
|
|
658
666
|
const authUrl = `${uiBaseUrl}/cli/auth?state=${encodeURIComponent(params.state)}&code_challenge=${encodeURIComponent(params.codeChallenge)}&code_challenge_method=S256&redirect_uri=${encodeURIComponent(redirectUri)}`;
|
|
@@ -668,8 +676,18 @@ program
|
|
|
668
676
|
(0, child_process_1.spawn)(openCommand, [authUrl], { detached: true, stdio: "ignore" }).unref();
|
|
669
677
|
// Step 4: Wait for callback
|
|
670
678
|
console.log("Waiting for authorization...");
|
|
679
|
+
console.log("(Press Ctrl+C to cancel)\n");
|
|
680
|
+
// Handle Ctrl+C gracefully
|
|
681
|
+
const cancelHandler = () => {
|
|
682
|
+
console.log("\n\nAuthentication cancelled by user.");
|
|
683
|
+
callbackServer.server.close();
|
|
684
|
+
process.exit(130); // Standard exit code for SIGINT
|
|
685
|
+
};
|
|
686
|
+
process.on("SIGINT", cancelHandler);
|
|
671
687
|
try {
|
|
672
688
|
const { code } = await callbackServer.promise;
|
|
689
|
+
// Remove the cancel handler after successful auth
|
|
690
|
+
process.off("SIGINT", cancelHandler);
|
|
673
691
|
// Step 5: Exchange code for token
|
|
674
692
|
console.log("\nExchanging authorization code for API token...");
|
|
675
693
|
const exchangeData = JSON.stringify({
|
|
@@ -685,19 +703,29 @@ program
|
|
|
685
703
|
"Content-Length": Buffer.byteLength(exchangeData),
|
|
686
704
|
},
|
|
687
705
|
}, (exchangeRes) => {
|
|
688
|
-
let
|
|
689
|
-
exchangeRes.on("data", (chunk) => (
|
|
706
|
+
let exchangeBody = "";
|
|
707
|
+
exchangeRes.on("data", (chunk) => (exchangeBody += chunk));
|
|
690
708
|
exchangeRes.on("end", () => {
|
|
691
709
|
if (exchangeRes.statusCode !== 200) {
|
|
692
710
|
console.error(`Failed to exchange code for token: ${exchangeRes.statusCode}`);
|
|
693
|
-
|
|
694
|
-
|
|
711
|
+
// Check if response is HTML (common for 404 pages)
|
|
712
|
+
if (exchangeBody.trim().startsWith("<!") || exchangeBody.trim().startsWith("<html")) {
|
|
713
|
+
console.error("Error: Received HTML response instead of JSON. This usually means:");
|
|
714
|
+
console.error(" 1. The API endpoint URL is incorrect");
|
|
715
|
+
console.error(" 2. The endpoint does not exist (404)");
|
|
716
|
+
console.error(`\nAPI URL attempted: ${exchangeUrl.toString()}`);
|
|
717
|
+
console.error("\nPlease verify the --api-base-url parameter.");
|
|
718
|
+
}
|
|
719
|
+
else {
|
|
720
|
+
console.error(exchangeBody);
|
|
721
|
+
}
|
|
722
|
+
process.exit(1);
|
|
695
723
|
return;
|
|
696
724
|
}
|
|
697
725
|
try {
|
|
698
|
-
const result = JSON.parse(
|
|
699
|
-
const apiToken = result.api_token;
|
|
700
|
-
const orgId = result.org_id;
|
|
726
|
+
const result = JSON.parse(exchangeBody);
|
|
727
|
+
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.
|
|
728
|
+
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.
|
|
701
729
|
// Step 6: Save token to config
|
|
702
730
|
config.writeConfig({
|
|
703
731
|
apiKey: apiToken,
|
|
@@ -708,32 +736,43 @@ program
|
|
|
708
736
|
console.log(`API key saved to: ${config.getConfigPath()}`);
|
|
709
737
|
console.log(`Organization ID: ${orgId}`);
|
|
710
738
|
console.log(`\nYou can now use the CLI without specifying an API key.`);
|
|
739
|
+
process.exit(0);
|
|
711
740
|
}
|
|
712
741
|
catch (err) {
|
|
713
742
|
const message = err instanceof Error ? err.message : String(err);
|
|
714
743
|
console.error(`Failed to parse response: ${message}`);
|
|
715
|
-
process.
|
|
744
|
+
process.exit(1);
|
|
716
745
|
}
|
|
717
746
|
});
|
|
718
747
|
});
|
|
719
748
|
exchangeReq.on("error", (err) => {
|
|
720
749
|
console.error(`Exchange request failed: ${err.message}`);
|
|
721
|
-
process.
|
|
750
|
+
process.exit(1);
|
|
722
751
|
});
|
|
723
752
|
exchangeReq.write(exchangeData);
|
|
724
753
|
exchangeReq.end();
|
|
725
754
|
}
|
|
726
755
|
catch (err) {
|
|
756
|
+
// Remove the cancel handler in error case too
|
|
757
|
+
process.off("SIGINT", cancelHandler);
|
|
727
758
|
const message = err instanceof Error ? err.message : String(err);
|
|
728
|
-
|
|
729
|
-
|
|
759
|
+
// Provide more helpful error messages
|
|
760
|
+
if (message.includes("timeout")) {
|
|
761
|
+
console.error(`\nAuthentication timed out.`);
|
|
762
|
+
console.error(`This usually means you closed the browser window without completing authentication.`);
|
|
763
|
+
console.error(`Please try again and complete the authentication flow.`);
|
|
764
|
+
}
|
|
765
|
+
else {
|
|
766
|
+
console.error(`\nAuthentication failed: ${message}`);
|
|
767
|
+
}
|
|
768
|
+
process.exit(1);
|
|
730
769
|
}
|
|
731
770
|
});
|
|
732
771
|
});
|
|
733
772
|
initReq.on("error", (err) => {
|
|
734
773
|
console.error(`Failed to connect to API: ${err.message}`);
|
|
735
774
|
callbackServer.server.close();
|
|
736
|
-
process.
|
|
775
|
+
process.exit(1);
|
|
737
776
|
});
|
|
738
777
|
initReq.write(initData);
|
|
739
778
|
initReq.end();
|
|
@@ -741,7 +780,7 @@ program
|
|
|
741
780
|
catch (err) {
|
|
742
781
|
const message = err instanceof Error ? err.message : String(err);
|
|
743
782
|
console.error(`Authentication error: ${message}`);
|
|
744
|
-
process.
|
|
783
|
+
process.exit(1);
|
|
745
784
|
}
|
|
746
785
|
});
|
|
747
786
|
program
|
|
@@ -761,15 +800,8 @@ program
|
|
|
761
800
|
console.log(`\nTo authenticate, run: pgai auth`);
|
|
762
801
|
return;
|
|
763
802
|
}
|
|
764
|
-
const
|
|
765
|
-
|
|
766
|
-
return "****";
|
|
767
|
-
if (k.length <= 16)
|
|
768
|
-
return `${k.slice(0, 4)}${"*".repeat(k.length - 8)}${k.slice(-4)}`;
|
|
769
|
-
// For longer keys, show more of the beginning to help identify them
|
|
770
|
-
return `${k.slice(0, Math.min(12, k.length - 8))}${"*".repeat(Math.max(4, k.length - 16))}${k.slice(-4)}`;
|
|
771
|
-
};
|
|
772
|
-
console.log(`Current API key: ${mask(cfg.apiKey)}`);
|
|
803
|
+
const { maskSecret } = require("../lib/util");
|
|
804
|
+
console.log(`Current API key: ${maskSecret(cfg.apiKey)}`);
|
|
773
805
|
if (cfg.orgId) {
|
|
774
806
|
console.log(`Organization ID: ${cfg.orgId}`);
|
|
775
807
|
}
|
|
@@ -811,9 +843,9 @@ program
|
|
|
811
843
|
console.log("API key removed");
|
|
812
844
|
console.log(`\nTo authenticate again, run: pgai auth`);
|
|
813
845
|
});
|
|
814
|
-
|
|
846
|
+
mon
|
|
815
847
|
.command("generate-grafana-password")
|
|
816
|
-
.description("generate Grafana password")
|
|
848
|
+
.description("generate Grafana password for monitoring services")
|
|
817
849
|
.action(async () => {
|
|
818
850
|
const cfgPath = path.resolve(process.cwd(), ".pgwatch-config");
|
|
819
851
|
try {
|
|
@@ -846,8 +878,8 @@ program
|
|
|
846
878
|
console.log(" URL: http://localhost:3000");
|
|
847
879
|
console.log(" Username: monitor");
|
|
848
880
|
console.log(` Password: ${newPassword}`);
|
|
849
|
-
console.log("\
|
|
850
|
-
console.log(" postgres-ai
|
|
881
|
+
console.log("\nReset Grafana to apply new password:");
|
|
882
|
+
console.log(" postgres-ai mon reset grafana");
|
|
851
883
|
}
|
|
852
884
|
catch (error) {
|
|
853
885
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -856,13 +888,13 @@ program
|
|
|
856
888
|
process.exitCode = 1;
|
|
857
889
|
}
|
|
858
890
|
});
|
|
859
|
-
|
|
891
|
+
mon
|
|
860
892
|
.command("show-grafana-credentials")
|
|
861
|
-
.description("show Grafana credentials")
|
|
893
|
+
.description("show Grafana credentials for monitoring services")
|
|
862
894
|
.action(async () => {
|
|
863
895
|
const cfgPath = path.resolve(process.cwd(), ".pgwatch-config");
|
|
864
896
|
if (!fs.existsSync(cfgPath)) {
|
|
865
|
-
console.error("Configuration file not found. Run 'quickstart' first.");
|
|
897
|
+
console.error("Configuration file not found. Run 'postgres-ai mon quickstart' first.");
|
|
866
898
|
process.exitCode = 1;
|
|
867
899
|
return;
|
|
868
900
|
}
|
|
@@ -893,5 +925,48 @@ program
|
|
|
893
925
|
console.log(` Password: ${password}`);
|
|
894
926
|
console.log("");
|
|
895
927
|
});
|
|
928
|
+
// Issues management
|
|
929
|
+
const issues = program.command("issues").description("issues management");
|
|
930
|
+
issues
|
|
931
|
+
.command("list")
|
|
932
|
+
.description("list issues")
|
|
933
|
+
.option("--debug", "enable debug output")
|
|
934
|
+
.action(async (opts) => {
|
|
935
|
+
try {
|
|
936
|
+
const rootOpts = program.opts();
|
|
937
|
+
const cfg = config.readConfig();
|
|
938
|
+
const { apiKey } = getConfig(rootOpts);
|
|
939
|
+
if (!apiKey) {
|
|
940
|
+
console.error("API key is required. Run 'pgai auth' first or set --api-key.");
|
|
941
|
+
process.exitCode = 1;
|
|
942
|
+
return;
|
|
943
|
+
}
|
|
944
|
+
const { apiBaseUrl } = (0, util_2.resolveBaseUrls)(rootOpts, cfg);
|
|
945
|
+
const result = await (0, issues_1.fetchIssues)({ apiKey, apiBaseUrl, debug: !!opts.debug });
|
|
946
|
+
if (typeof result === "string") {
|
|
947
|
+
process.stdout.write(result);
|
|
948
|
+
if (!/\n$/.test(result))
|
|
949
|
+
console.log();
|
|
950
|
+
}
|
|
951
|
+
else {
|
|
952
|
+
console.log(JSON.stringify(result, null, 2));
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
catch (err) {
|
|
956
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
957
|
+
console.error(message);
|
|
958
|
+
process.exitCode = 1;
|
|
959
|
+
}
|
|
960
|
+
});
|
|
961
|
+
// MCP server
|
|
962
|
+
const mcp = program.command("mcp").description("MCP server integration");
|
|
963
|
+
mcp
|
|
964
|
+
.command("start")
|
|
965
|
+
.description("start MCP stdio server")
|
|
966
|
+
.option("--debug", "enable debug output")
|
|
967
|
+
.action(async (opts) => {
|
|
968
|
+
const rootOpts = program.opts();
|
|
969
|
+
await (0, mcp_server_1.startMcpServer)(rootOpts, { debug: !!opts.debug });
|
|
970
|
+
});
|
|
896
971
|
program.parseAsync(process.argv);
|
|
897
972
|
//# sourceMappingURL=postgres-ai.js.map
|