postgresai 0.11.0-alpha.9 → 0.12.0-alpha.14
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 +76 -18
- package/bin/postgres-ai.ts +258 -124
- package/dist/bin/postgres-ai.js +237 -117
- 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 +5 -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 +5 -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
|
/**
|
|
@@ -87,10 +90,20 @@ const stub = (name) => async () => {
|
|
|
87
90
|
* Resolve project paths
|
|
88
91
|
*/
|
|
89
92
|
function resolvePaths() {
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
93
|
+
const startDir = process.cwd();
|
|
94
|
+
let currentDir = startDir;
|
|
95
|
+
while (true) {
|
|
96
|
+
const composeFile = path.resolve(currentDir, "docker-compose.yml");
|
|
97
|
+
if (fs.existsSync(composeFile)) {
|
|
98
|
+
const instancesFile = path.resolve(currentDir, "instances.yml");
|
|
99
|
+
return { fs, path, projectDir: currentDir, composeFile, instancesFile };
|
|
100
|
+
}
|
|
101
|
+
const parentDir = path.dirname(currentDir);
|
|
102
|
+
if (parentDir === currentDir)
|
|
103
|
+
break;
|
|
104
|
+
currentDir = parentDir;
|
|
105
|
+
}
|
|
106
|
+
throw new Error(`docker-compose.yml not found. Run monitoring commands from the PostgresAI project directory or one of its subdirectories (starting search from ${startDir}).`);
|
|
94
107
|
}
|
|
95
108
|
/**
|
|
96
109
|
* Get docker compose command
|
|
@@ -107,7 +120,16 @@ function getComposeCmd() {
|
|
|
107
120
|
* Run docker compose command
|
|
108
121
|
*/
|
|
109
122
|
async function runCompose(args) {
|
|
110
|
-
|
|
123
|
+
let composeFile;
|
|
124
|
+
try {
|
|
125
|
+
({ composeFile } = resolvePaths());
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
129
|
+
console.error(message);
|
|
130
|
+
process.exitCode = 1;
|
|
131
|
+
return 1;
|
|
132
|
+
}
|
|
111
133
|
const cmd = getComposeCmd();
|
|
112
134
|
if (!cmd) {
|
|
113
135
|
console.error("docker compose not found (need docker-compose or docker compose)");
|
|
@@ -122,10 +144,11 @@ async function runCompose(args) {
|
|
|
122
144
|
program.command("help", { isDefault: true }).description("show help").action(() => {
|
|
123
145
|
program.outputHelp();
|
|
124
146
|
});
|
|
125
|
-
//
|
|
126
|
-
program
|
|
147
|
+
// Monitoring services management
|
|
148
|
+
const mon = program.command("mon").description("monitoring services management");
|
|
149
|
+
mon
|
|
127
150
|
.command("quickstart")
|
|
128
|
-
.description("complete setup (generate config, start services)")
|
|
151
|
+
.description("complete setup (generate config, start monitoring services)")
|
|
129
152
|
.option("--demo", "demo mode", false)
|
|
130
153
|
.action(async () => {
|
|
131
154
|
const code1 = await runCompose(["run", "--rm", "sources-generator"]);
|
|
@@ -137,31 +160,25 @@ program
|
|
|
137
160
|
if (code2 !== 0)
|
|
138
161
|
process.exitCode = code2;
|
|
139
162
|
});
|
|
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
|
|
163
|
+
mon
|
|
147
164
|
.command("start")
|
|
148
|
-
.description("start services")
|
|
165
|
+
.description("start monitoring services")
|
|
149
166
|
.action(async () => {
|
|
150
167
|
const code = await runCompose(["up", "-d"]);
|
|
151
168
|
if (code !== 0)
|
|
152
169
|
process.exitCode = code;
|
|
153
170
|
});
|
|
154
|
-
|
|
171
|
+
mon
|
|
155
172
|
.command("stop")
|
|
156
|
-
.description("stop services")
|
|
173
|
+
.description("stop monitoring services")
|
|
157
174
|
.action(async () => {
|
|
158
175
|
const code = await runCompose(["down"]);
|
|
159
176
|
if (code !== 0)
|
|
160
177
|
process.exitCode = code;
|
|
161
178
|
});
|
|
162
|
-
|
|
179
|
+
mon
|
|
163
180
|
.command("restart [service]")
|
|
164
|
-
.description("restart all services or specific service")
|
|
181
|
+
.description("restart all monitoring services or specific service")
|
|
165
182
|
.action(async (service) => {
|
|
166
183
|
const args = ["restart"];
|
|
167
184
|
if (service)
|
|
@@ -170,19 +187,19 @@ program
|
|
|
170
187
|
if (code !== 0)
|
|
171
188
|
process.exitCode = code;
|
|
172
189
|
});
|
|
173
|
-
|
|
190
|
+
mon
|
|
174
191
|
.command("status")
|
|
175
|
-
.description("show
|
|
192
|
+
.description("show monitoring services status")
|
|
176
193
|
.action(async () => {
|
|
177
194
|
const code = await runCompose(["ps"]);
|
|
178
195
|
if (code !== 0)
|
|
179
196
|
process.exitCode = code;
|
|
180
197
|
});
|
|
181
|
-
|
|
198
|
+
mon
|
|
182
199
|
.command("logs [service]")
|
|
183
200
|
.option("-f, --follow", "follow logs", false)
|
|
184
201
|
.option("--tail <lines>", "number of lines to show from the end of logs", "all")
|
|
185
|
-
.description("show logs for all or specific service")
|
|
202
|
+
.description("show logs for all or specific monitoring service")
|
|
186
203
|
.action(async (service, opts) => {
|
|
187
204
|
const args = ["logs"];
|
|
188
205
|
if (opts.follow)
|
|
@@ -195,9 +212,9 @@ program
|
|
|
195
212
|
if (code !== 0)
|
|
196
213
|
process.exitCode = code;
|
|
197
214
|
});
|
|
198
|
-
|
|
215
|
+
mon
|
|
199
216
|
.command("health")
|
|
200
|
-
.description("health check")
|
|
217
|
+
.description("health check for monitoring services")
|
|
201
218
|
.option("--wait <seconds>", "wait time in seconds for services to become healthy", parseInt, 0)
|
|
202
219
|
.action(async (opts) => {
|
|
203
220
|
const services = [
|
|
@@ -218,13 +235,19 @@ program
|
|
|
218
235
|
allHealthy = true;
|
|
219
236
|
for (const service of services) {
|
|
220
237
|
try {
|
|
221
|
-
|
|
222
|
-
const
|
|
223
|
-
|
|
238
|
+
// Use native fetch instead of requiring curl to be installed
|
|
239
|
+
const controller = new AbortController();
|
|
240
|
+
const timeoutId = setTimeout(() => controller.abort(), 5000);
|
|
241
|
+
const response = await fetch(service.url, {
|
|
242
|
+
signal: controller.signal,
|
|
243
|
+
method: 'GET',
|
|
244
|
+
});
|
|
245
|
+
clearTimeout(timeoutId);
|
|
246
|
+
if (response.status === 200) {
|
|
224
247
|
console.log(`✓ ${service.name}: healthy`);
|
|
225
248
|
}
|
|
226
249
|
else {
|
|
227
|
-
console.log(`✗ ${service.name}: unhealthy (HTTP ${
|
|
250
|
+
console.log(`✗ ${service.name}: unhealthy (HTTP ${response.status})`);
|
|
228
251
|
allHealthy = false;
|
|
229
252
|
}
|
|
230
253
|
}
|
|
@@ -246,11 +269,22 @@ program
|
|
|
246
269
|
process.exitCode = 1;
|
|
247
270
|
}
|
|
248
271
|
});
|
|
249
|
-
|
|
272
|
+
mon
|
|
250
273
|
.command("config")
|
|
251
|
-
.description("show configuration")
|
|
274
|
+
.description("show monitoring services configuration")
|
|
252
275
|
.action(async () => {
|
|
253
|
-
|
|
276
|
+
let projectDir;
|
|
277
|
+
let composeFile;
|
|
278
|
+
let instancesFile;
|
|
279
|
+
try {
|
|
280
|
+
({ projectDir, composeFile, instancesFile } = resolvePaths());
|
|
281
|
+
}
|
|
282
|
+
catch (error) {
|
|
283
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
284
|
+
console.error(message);
|
|
285
|
+
process.exitCode = 1;
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
254
288
|
console.log(`Project Directory: ${projectDir}`);
|
|
255
289
|
console.log(`Docker Compose File: ${composeFile}`);
|
|
256
290
|
console.log(`Instances File: ${instancesFile}`);
|
|
@@ -262,17 +296,17 @@ program
|
|
|
262
296
|
console.log();
|
|
263
297
|
}
|
|
264
298
|
});
|
|
265
|
-
|
|
299
|
+
mon
|
|
266
300
|
.command("update-config")
|
|
267
|
-
.description("apply configuration (generate sources)")
|
|
301
|
+
.description("apply monitoring services configuration (generate sources)")
|
|
268
302
|
.action(async () => {
|
|
269
303
|
const code = await runCompose(["run", "--rm", "sources-generator"]);
|
|
270
304
|
if (code !== 0)
|
|
271
305
|
process.exitCode = code;
|
|
272
306
|
});
|
|
273
|
-
|
|
307
|
+
mon
|
|
274
308
|
.command("update")
|
|
275
|
-
.description("update
|
|
309
|
+
.description("update monitoring stack")
|
|
276
310
|
.action(async () => {
|
|
277
311
|
console.log("Updating PostgresAI monitoring stack...\n");
|
|
278
312
|
try {
|
|
@@ -299,8 +333,8 @@ program
|
|
|
299
333
|
const code = await runCompose(["pull"]);
|
|
300
334
|
if (code === 0) {
|
|
301
335
|
console.log("\n✓ Update completed successfully");
|
|
302
|
-
console.log("\nTo apply updates, restart services:");
|
|
303
|
-
console.log(" postgres-ai restart");
|
|
336
|
+
console.log("\nTo apply updates, restart monitoring services:");
|
|
337
|
+
console.log(" postgres-ai mon restart");
|
|
304
338
|
}
|
|
305
339
|
else {
|
|
306
340
|
console.error("\n✗ Docker image update failed");
|
|
@@ -313,9 +347,9 @@ program
|
|
|
313
347
|
process.exitCode = 1;
|
|
314
348
|
}
|
|
315
349
|
});
|
|
316
|
-
|
|
350
|
+
mon
|
|
317
351
|
.command("reset [service]")
|
|
318
|
-
.description("reset all or specific service")
|
|
352
|
+
.description("reset all or specific monitoring service")
|
|
319
353
|
.action(async (service) => {
|
|
320
354
|
const rl = readline.createInterface({
|
|
321
355
|
input: process.stdin,
|
|
@@ -376,9 +410,9 @@ program
|
|
|
376
410
|
process.exitCode = 1;
|
|
377
411
|
}
|
|
378
412
|
});
|
|
379
|
-
|
|
413
|
+
mon
|
|
380
414
|
.command("clean")
|
|
381
|
-
.description("cleanup artifacts")
|
|
415
|
+
.description("cleanup monitoring services artifacts")
|
|
382
416
|
.action(async () => {
|
|
383
417
|
console.log("Cleaning up Docker resources...\n");
|
|
384
418
|
try {
|
|
@@ -409,26 +443,27 @@ program
|
|
|
409
443
|
process.exitCode = 1;
|
|
410
444
|
}
|
|
411
445
|
});
|
|
412
|
-
|
|
446
|
+
mon
|
|
413
447
|
.command("shell <service>")
|
|
414
|
-
.description("open service
|
|
448
|
+
.description("open shell to monitoring service")
|
|
415
449
|
.action(async (service) => {
|
|
416
450
|
const code = await runCompose(["exec", service, "/bin/sh"]);
|
|
417
451
|
if (code !== 0)
|
|
418
452
|
process.exitCode = code;
|
|
419
453
|
});
|
|
420
|
-
|
|
454
|
+
mon
|
|
421
455
|
.command("check")
|
|
422
|
-
.description("system readiness check")
|
|
456
|
+
.description("monitoring services system readiness check")
|
|
423
457
|
.action(async () => {
|
|
424
458
|
const code = await runCompose(["ps"]);
|
|
425
459
|
if (code !== 0)
|
|
426
460
|
process.exitCode = code;
|
|
427
461
|
});
|
|
428
|
-
//
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
.
|
|
462
|
+
// Monitoring targets (databases to monitor)
|
|
463
|
+
const targets = mon.command("targets").description("manage databases to monitor");
|
|
464
|
+
targets
|
|
465
|
+
.command("list")
|
|
466
|
+
.description("list monitoring target databases")
|
|
432
467
|
.action(async () => {
|
|
433
468
|
const instancesPath = path.resolve(process.cwd(), "instances.yml");
|
|
434
469
|
if (!fs.existsSync(instancesPath)) {
|
|
@@ -440,29 +475,29 @@ program
|
|
|
440
475
|
const content = fs.readFileSync(instancesPath, "utf8");
|
|
441
476
|
const instances = yaml.load(content);
|
|
442
477
|
if (!instances || !Array.isArray(instances) || instances.length === 0) {
|
|
443
|
-
console.log("No
|
|
478
|
+
console.log("No monitoring targets configured");
|
|
444
479
|
console.log("");
|
|
445
|
-
console.log("To add
|
|
446
|
-
console.log(" postgres-ai add
|
|
480
|
+
console.log("To add a monitoring target:");
|
|
481
|
+
console.log(" postgres-ai mon targets add <connection-string> <name>");
|
|
447
482
|
console.log("");
|
|
448
483
|
console.log("Example:");
|
|
449
|
-
console.log(" postgres-ai add
|
|
484
|
+
console.log(" postgres-ai mon targets add 'postgresql://user:pass@host:5432/db' my-db");
|
|
450
485
|
return;
|
|
451
486
|
}
|
|
452
|
-
// Filter out demo
|
|
453
|
-
const filtered = instances.filter((inst) => inst.name && inst.
|
|
487
|
+
// Filter out disabled instances (e.g., demo placeholders)
|
|
488
|
+
const filtered = instances.filter((inst) => inst.name && inst.is_enabled !== false);
|
|
454
489
|
if (filtered.length === 0) {
|
|
455
|
-
console.log("No
|
|
490
|
+
console.log("No monitoring targets configured");
|
|
456
491
|
console.log("");
|
|
457
|
-
console.log("To add
|
|
458
|
-
console.log(" postgres-ai add
|
|
492
|
+
console.log("To add a monitoring target:");
|
|
493
|
+
console.log(" postgres-ai mon targets add <connection-string> <name>");
|
|
459
494
|
console.log("");
|
|
460
495
|
console.log("Example:");
|
|
461
|
-
console.log(" postgres-ai add
|
|
496
|
+
console.log(" postgres-ai mon targets add 'postgresql://user:pass@host:5432/db' my-db");
|
|
462
497
|
return;
|
|
463
498
|
}
|
|
464
499
|
for (const inst of filtered) {
|
|
465
|
-
console.log(`
|
|
500
|
+
console.log(`Target: ${inst.name}`);
|
|
466
501
|
}
|
|
467
502
|
}
|
|
468
503
|
catch (err) {
|
|
@@ -471,9 +506,9 @@ program
|
|
|
471
506
|
process.exitCode = 1;
|
|
472
507
|
}
|
|
473
508
|
});
|
|
474
|
-
|
|
475
|
-
.command("add
|
|
476
|
-
.description("add
|
|
509
|
+
targets
|
|
510
|
+
.command("add [connStr] [name]")
|
|
511
|
+
.description("add monitoring target database")
|
|
477
512
|
.action(async (connStr, name) => {
|
|
478
513
|
const file = path.resolve(process.cwd(), "instances.yml");
|
|
479
514
|
if (!connStr) {
|
|
@@ -498,7 +533,7 @@ program
|
|
|
498
533
|
if (Array.isArray(instances)) {
|
|
499
534
|
const exists = instances.some((inst) => inst.name === instanceName);
|
|
500
535
|
if (exists) {
|
|
501
|
-
console.error(`
|
|
536
|
+
console.error(`Monitoring target '${instanceName}' already exists`);
|
|
502
537
|
process.exitCode = 1;
|
|
503
538
|
return;
|
|
504
539
|
}
|
|
@@ -509,7 +544,7 @@ program
|
|
|
509
544
|
// If YAML parsing fails, fall back to simple check
|
|
510
545
|
const content = fs.existsSync(file) ? fs.readFileSync(file, "utf8") : "";
|
|
511
546
|
if (new RegExp(`^- name: ${instanceName}$`, "m").test(content)) {
|
|
512
|
-
console.error(`
|
|
547
|
+
console.error(`Monitoring target '${instanceName}' already exists`);
|
|
513
548
|
process.exitCode = 1;
|
|
514
549
|
return;
|
|
515
550
|
}
|
|
@@ -518,11 +553,11 @@ program
|
|
|
518
553
|
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
554
|
const content = fs.existsSync(file) ? fs.readFileSync(file, "utf8") : "";
|
|
520
555
|
fs.appendFileSync(file, (content && !/\n$/.test(content) ? "\n" : "") + body, "utf8");
|
|
521
|
-
console.log(`
|
|
556
|
+
console.log(`Monitoring target '${instanceName}' added`);
|
|
522
557
|
});
|
|
523
|
-
|
|
524
|
-
.command("remove
|
|
525
|
-
.description("remove
|
|
558
|
+
targets
|
|
559
|
+
.command("remove <name>")
|
|
560
|
+
.description("remove monitoring target database")
|
|
526
561
|
.action(async (name) => {
|
|
527
562
|
const file = path.resolve(process.cwd(), "instances.yml");
|
|
528
563
|
if (!fs.existsSync(file)) {
|
|
@@ -540,12 +575,12 @@ program
|
|
|
540
575
|
}
|
|
541
576
|
const filtered = instances.filter((inst) => inst.name !== name);
|
|
542
577
|
if (filtered.length === instances.length) {
|
|
543
|
-
console.error(`
|
|
578
|
+
console.error(`Monitoring target '${name}' not found`);
|
|
544
579
|
process.exitCode = 1;
|
|
545
580
|
return;
|
|
546
581
|
}
|
|
547
582
|
fs.writeFileSync(file, yaml.dump(filtered), "utf8");
|
|
548
|
-
console.log(`
|
|
583
|
+
console.log(`Monitoring target '${name}' removed`);
|
|
549
584
|
}
|
|
550
585
|
catch (err) {
|
|
551
586
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -553,9 +588,9 @@ program
|
|
|
553
588
|
process.exitCode = 1;
|
|
554
589
|
}
|
|
555
590
|
});
|
|
556
|
-
|
|
557
|
-
.command("test
|
|
558
|
-
.description("test
|
|
591
|
+
targets
|
|
592
|
+
.command("test <name>")
|
|
593
|
+
.description("test monitoring target database connectivity")
|
|
559
594
|
.action(async (name) => {
|
|
560
595
|
const instancesPath = path.resolve(process.cwd(), "instances.yml");
|
|
561
596
|
if (!fs.existsSync(instancesPath)) {
|
|
@@ -573,19 +608,28 @@ program
|
|
|
573
608
|
}
|
|
574
609
|
const instance = instances.find((inst) => inst.name === name);
|
|
575
610
|
if (!instance) {
|
|
576
|
-
console.error(`
|
|
611
|
+
console.error(`Monitoring target '${name}' not found`);
|
|
577
612
|
process.exitCode = 1;
|
|
578
613
|
return;
|
|
579
614
|
}
|
|
580
615
|
if (!instance.conn_str) {
|
|
581
|
-
console.error(`Connection string not found for
|
|
616
|
+
console.error(`Connection string not found for monitoring target '${name}'`);
|
|
582
617
|
process.exitCode = 1;
|
|
583
618
|
return;
|
|
584
619
|
}
|
|
585
|
-
console.log(`Testing connection to '${name}'...`);
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
620
|
+
console.log(`Testing connection to monitoring target '${name}'...`);
|
|
621
|
+
// Use native pg client instead of requiring psql to be installed
|
|
622
|
+
const { Client } = require('pg');
|
|
623
|
+
const client = new Client({ connectionString: instance.conn_str });
|
|
624
|
+
try {
|
|
625
|
+
await client.connect();
|
|
626
|
+
const result = await client.query('select version();');
|
|
627
|
+
console.log(`✓ Connection successful`);
|
|
628
|
+
console.log(result.rows[0].version);
|
|
629
|
+
}
|
|
630
|
+
finally {
|
|
631
|
+
await client.end();
|
|
632
|
+
}
|
|
589
633
|
}
|
|
590
634
|
catch (error) {
|
|
591
635
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -606,8 +650,8 @@ program
|
|
|
606
650
|
// Generate PKCE parameters
|
|
607
651
|
const params = pkce.generatePKCEParams();
|
|
608
652
|
const rootOpts = program.opts();
|
|
609
|
-
const
|
|
610
|
-
const
|
|
653
|
+
const cfg = config.readConfig();
|
|
654
|
+
const { apiBaseUrl, uiBaseUrl } = (0, util_2.resolveBaseUrls)(rootOpts, cfg);
|
|
611
655
|
if (opts.debug) {
|
|
612
656
|
console.log(`Debug: Resolved API base URL: ${apiBaseUrl}`);
|
|
613
657
|
console.log(`Debug: Resolved UI base URL: ${uiBaseUrl}`);
|
|
@@ -616,7 +660,7 @@ program
|
|
|
616
660
|
// Step 1: Start local callback server FIRST to get actual port
|
|
617
661
|
console.log("Starting local callback server...");
|
|
618
662
|
const requestedPort = opts.port || 0; // 0 = OS assigns available port
|
|
619
|
-
const callbackServer = authServer.createCallbackServer(requestedPort, params.state,
|
|
663
|
+
const callbackServer = authServer.createCallbackServer(requestedPort, params.state, 120000); // 2 minute timeout
|
|
620
664
|
// Wait a bit for server to start and get port
|
|
621
665
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
622
666
|
const actualPort = callbackServer.getPort();
|
|
@@ -649,10 +693,19 @@ program
|
|
|
649
693
|
res.on("end", async () => {
|
|
650
694
|
if (res.statusCode !== 200) {
|
|
651
695
|
console.error(`Failed to initialize auth session: ${res.statusCode}`);
|
|
652
|
-
|
|
696
|
+
// Check if response is HTML (common for 404 pages)
|
|
697
|
+
if (data.trim().startsWith("<!") || data.trim().startsWith("<html")) {
|
|
698
|
+
console.error("Error: Received HTML response instead of JSON. This usually means:");
|
|
699
|
+
console.error(" 1. The API endpoint URL is incorrect");
|
|
700
|
+
console.error(" 2. The endpoint does not exist (404)");
|
|
701
|
+
console.error(`\nAPI URL attempted: ${initUrl.toString()}`);
|
|
702
|
+
console.error("\nPlease verify the --api-base-url parameter.");
|
|
703
|
+
}
|
|
704
|
+
else {
|
|
705
|
+
console.error(data);
|
|
706
|
+
}
|
|
653
707
|
callbackServer.server.close();
|
|
654
|
-
process.
|
|
655
|
-
return;
|
|
708
|
+
process.exit(1);
|
|
656
709
|
}
|
|
657
710
|
// Step 3: Open browser
|
|
658
711
|
const authUrl = `${uiBaseUrl}/cli/auth?state=${encodeURIComponent(params.state)}&code_challenge=${encodeURIComponent(params.codeChallenge)}&code_challenge_method=S256&redirect_uri=${encodeURIComponent(redirectUri)}`;
|
|
@@ -668,8 +721,18 @@ program
|
|
|
668
721
|
(0, child_process_1.spawn)(openCommand, [authUrl], { detached: true, stdio: "ignore" }).unref();
|
|
669
722
|
// Step 4: Wait for callback
|
|
670
723
|
console.log("Waiting for authorization...");
|
|
724
|
+
console.log("(Press Ctrl+C to cancel)\n");
|
|
725
|
+
// Handle Ctrl+C gracefully
|
|
726
|
+
const cancelHandler = () => {
|
|
727
|
+
console.log("\n\nAuthentication cancelled by user.");
|
|
728
|
+
callbackServer.server.close();
|
|
729
|
+
process.exit(130); // Standard exit code for SIGINT
|
|
730
|
+
};
|
|
731
|
+
process.on("SIGINT", cancelHandler);
|
|
671
732
|
try {
|
|
672
733
|
const { code } = await callbackServer.promise;
|
|
734
|
+
// Remove the cancel handler after successful auth
|
|
735
|
+
process.off("SIGINT", cancelHandler);
|
|
673
736
|
// Step 5: Exchange code for token
|
|
674
737
|
console.log("\nExchanging authorization code for API token...");
|
|
675
738
|
const exchangeData = JSON.stringify({
|
|
@@ -685,19 +748,29 @@ program
|
|
|
685
748
|
"Content-Length": Buffer.byteLength(exchangeData),
|
|
686
749
|
},
|
|
687
750
|
}, (exchangeRes) => {
|
|
688
|
-
let
|
|
689
|
-
exchangeRes.on("data", (chunk) => (
|
|
751
|
+
let exchangeBody = "";
|
|
752
|
+
exchangeRes.on("data", (chunk) => (exchangeBody += chunk));
|
|
690
753
|
exchangeRes.on("end", () => {
|
|
691
754
|
if (exchangeRes.statusCode !== 200) {
|
|
692
755
|
console.error(`Failed to exchange code for token: ${exchangeRes.statusCode}`);
|
|
693
|
-
|
|
694
|
-
|
|
756
|
+
// Check if response is HTML (common for 404 pages)
|
|
757
|
+
if (exchangeBody.trim().startsWith("<!") || exchangeBody.trim().startsWith("<html")) {
|
|
758
|
+
console.error("Error: Received HTML response instead of JSON. This usually means:");
|
|
759
|
+
console.error(" 1. The API endpoint URL is incorrect");
|
|
760
|
+
console.error(" 2. The endpoint does not exist (404)");
|
|
761
|
+
console.error(`\nAPI URL attempted: ${exchangeUrl.toString()}`);
|
|
762
|
+
console.error("\nPlease verify the --api-base-url parameter.");
|
|
763
|
+
}
|
|
764
|
+
else {
|
|
765
|
+
console.error(exchangeBody);
|
|
766
|
+
}
|
|
767
|
+
process.exit(1);
|
|
695
768
|
return;
|
|
696
769
|
}
|
|
697
770
|
try {
|
|
698
|
-
const result = JSON.parse(
|
|
699
|
-
const apiToken = result.api_token;
|
|
700
|
-
const orgId = result.org_id;
|
|
771
|
+
const result = JSON.parse(exchangeBody);
|
|
772
|
+
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.
|
|
773
|
+
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
774
|
// Step 6: Save token to config
|
|
702
775
|
config.writeConfig({
|
|
703
776
|
apiKey: apiToken,
|
|
@@ -708,32 +781,43 @@ program
|
|
|
708
781
|
console.log(`API key saved to: ${config.getConfigPath()}`);
|
|
709
782
|
console.log(`Organization ID: ${orgId}`);
|
|
710
783
|
console.log(`\nYou can now use the CLI without specifying an API key.`);
|
|
784
|
+
process.exit(0);
|
|
711
785
|
}
|
|
712
786
|
catch (err) {
|
|
713
787
|
const message = err instanceof Error ? err.message : String(err);
|
|
714
788
|
console.error(`Failed to parse response: ${message}`);
|
|
715
|
-
process.
|
|
789
|
+
process.exit(1);
|
|
716
790
|
}
|
|
717
791
|
});
|
|
718
792
|
});
|
|
719
793
|
exchangeReq.on("error", (err) => {
|
|
720
794
|
console.error(`Exchange request failed: ${err.message}`);
|
|
721
|
-
process.
|
|
795
|
+
process.exit(1);
|
|
722
796
|
});
|
|
723
797
|
exchangeReq.write(exchangeData);
|
|
724
798
|
exchangeReq.end();
|
|
725
799
|
}
|
|
726
800
|
catch (err) {
|
|
801
|
+
// Remove the cancel handler in error case too
|
|
802
|
+
process.off("SIGINT", cancelHandler);
|
|
727
803
|
const message = err instanceof Error ? err.message : String(err);
|
|
728
|
-
|
|
729
|
-
|
|
804
|
+
// Provide more helpful error messages
|
|
805
|
+
if (message.includes("timeout")) {
|
|
806
|
+
console.error(`\nAuthentication timed out.`);
|
|
807
|
+
console.error(`This usually means you closed the browser window without completing authentication.`);
|
|
808
|
+
console.error(`Please try again and complete the authentication flow.`);
|
|
809
|
+
}
|
|
810
|
+
else {
|
|
811
|
+
console.error(`\nAuthentication failed: ${message}`);
|
|
812
|
+
}
|
|
813
|
+
process.exit(1);
|
|
730
814
|
}
|
|
731
815
|
});
|
|
732
816
|
});
|
|
733
817
|
initReq.on("error", (err) => {
|
|
734
818
|
console.error(`Failed to connect to API: ${err.message}`);
|
|
735
819
|
callbackServer.server.close();
|
|
736
|
-
process.
|
|
820
|
+
process.exit(1);
|
|
737
821
|
});
|
|
738
822
|
initReq.write(initData);
|
|
739
823
|
initReq.end();
|
|
@@ -741,7 +825,7 @@ program
|
|
|
741
825
|
catch (err) {
|
|
742
826
|
const message = err instanceof Error ? err.message : String(err);
|
|
743
827
|
console.error(`Authentication error: ${message}`);
|
|
744
|
-
process.
|
|
828
|
+
process.exit(1);
|
|
745
829
|
}
|
|
746
830
|
});
|
|
747
831
|
program
|
|
@@ -761,15 +845,8 @@ program
|
|
|
761
845
|
console.log(`\nTo authenticate, run: pgai auth`);
|
|
762
846
|
return;
|
|
763
847
|
}
|
|
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)}`);
|
|
848
|
+
const { maskSecret } = require("../lib/util");
|
|
849
|
+
console.log(`Current API key: ${maskSecret(cfg.apiKey)}`);
|
|
773
850
|
if (cfg.orgId) {
|
|
774
851
|
console.log(`Organization ID: ${cfg.orgId}`);
|
|
775
852
|
}
|
|
@@ -811,9 +888,9 @@ program
|
|
|
811
888
|
console.log("API key removed");
|
|
812
889
|
console.log(`\nTo authenticate again, run: pgai auth`);
|
|
813
890
|
});
|
|
814
|
-
|
|
891
|
+
mon
|
|
815
892
|
.command("generate-grafana-password")
|
|
816
|
-
.description("generate Grafana password")
|
|
893
|
+
.description("generate Grafana password for monitoring services")
|
|
817
894
|
.action(async () => {
|
|
818
895
|
const cfgPath = path.resolve(process.cwd(), ".pgwatch-config");
|
|
819
896
|
try {
|
|
@@ -846,8 +923,8 @@ program
|
|
|
846
923
|
console.log(" URL: http://localhost:3000");
|
|
847
924
|
console.log(" Username: monitor");
|
|
848
925
|
console.log(` Password: ${newPassword}`);
|
|
849
|
-
console.log("\
|
|
850
|
-
console.log(" postgres-ai
|
|
926
|
+
console.log("\nReset Grafana to apply new password:");
|
|
927
|
+
console.log(" postgres-ai mon reset grafana");
|
|
851
928
|
}
|
|
852
929
|
catch (error) {
|
|
853
930
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -856,13 +933,13 @@ program
|
|
|
856
933
|
process.exitCode = 1;
|
|
857
934
|
}
|
|
858
935
|
});
|
|
859
|
-
|
|
936
|
+
mon
|
|
860
937
|
.command("show-grafana-credentials")
|
|
861
|
-
.description("show Grafana credentials")
|
|
938
|
+
.description("show Grafana credentials for monitoring services")
|
|
862
939
|
.action(async () => {
|
|
863
940
|
const cfgPath = path.resolve(process.cwd(), ".pgwatch-config");
|
|
864
941
|
if (!fs.existsSync(cfgPath)) {
|
|
865
|
-
console.error("Configuration file not found. Run 'quickstart' first.");
|
|
942
|
+
console.error("Configuration file not found. Run 'postgres-ai mon quickstart' first.");
|
|
866
943
|
process.exitCode = 1;
|
|
867
944
|
return;
|
|
868
945
|
}
|
|
@@ -893,5 +970,48 @@ program
|
|
|
893
970
|
console.log(` Password: ${password}`);
|
|
894
971
|
console.log("");
|
|
895
972
|
});
|
|
973
|
+
// Issues management
|
|
974
|
+
const issues = program.command("issues").description("issues management");
|
|
975
|
+
issues
|
|
976
|
+
.command("list")
|
|
977
|
+
.description("list issues")
|
|
978
|
+
.option("--debug", "enable debug output")
|
|
979
|
+
.action(async (opts) => {
|
|
980
|
+
try {
|
|
981
|
+
const rootOpts = program.opts();
|
|
982
|
+
const cfg = config.readConfig();
|
|
983
|
+
const { apiKey } = getConfig(rootOpts);
|
|
984
|
+
if (!apiKey) {
|
|
985
|
+
console.error("API key is required. Run 'pgai auth' first or set --api-key.");
|
|
986
|
+
process.exitCode = 1;
|
|
987
|
+
return;
|
|
988
|
+
}
|
|
989
|
+
const { apiBaseUrl } = (0, util_2.resolveBaseUrls)(rootOpts, cfg);
|
|
990
|
+
const result = await (0, issues_1.fetchIssues)({ apiKey, apiBaseUrl, debug: !!opts.debug });
|
|
991
|
+
if (typeof result === "string") {
|
|
992
|
+
process.stdout.write(result);
|
|
993
|
+
if (!/\n$/.test(result))
|
|
994
|
+
console.log();
|
|
995
|
+
}
|
|
996
|
+
else {
|
|
997
|
+
console.log(JSON.stringify(result, null, 2));
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
catch (err) {
|
|
1001
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1002
|
+
console.error(message);
|
|
1003
|
+
process.exitCode = 1;
|
|
1004
|
+
}
|
|
1005
|
+
});
|
|
1006
|
+
// MCP server
|
|
1007
|
+
const mcp = program.command("mcp").description("MCP server integration");
|
|
1008
|
+
mcp
|
|
1009
|
+
.command("start")
|
|
1010
|
+
.description("start MCP stdio server")
|
|
1011
|
+
.option("--debug", "enable debug output")
|
|
1012
|
+
.action(async (opts) => {
|
|
1013
|
+
const rootOpts = program.opts();
|
|
1014
|
+
await (0, mcp_server_1.startMcpServer)(rootOpts, { debug: !!opts.debug });
|
|
1015
|
+
});
|
|
896
1016
|
program.parseAsync(process.argv);
|
|
897
1017
|
//# sourceMappingURL=postgres-ai.js.map
|