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.
@@ -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 projectDir = process.cwd();
91
- const composeFile = path.resolve(projectDir, "docker-compose.yml");
92
- const instancesFile = path.resolve(projectDir, "instances.yml");
93
- return { fs, path, projectDir, composeFile, instancesFile };
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
- const { composeFile } = resolvePaths();
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
- // Service lifecycle
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
- program
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
- program
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
- program
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
- program
190
+ mon
174
191
  .command("status")
175
- .description("show service status")
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
- program
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
- program
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
- const { stdout } = await execPromise(`curl -sf -o /dev/null -w "%{http_code}" ${service.url}`, { timeout: 5000 });
222
- const code = stdout.trim();
223
- if (code === "200") {
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 ${code})`);
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
- program
272
+ mon
250
273
  .command("config")
251
- .description("show configuration")
274
+ .description("show monitoring services configuration")
252
275
  .action(async () => {
253
- const { fs, projectDir, composeFile, instancesFile } = resolvePaths();
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
- program
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
- program
307
+ mon
274
308
  .command("update")
275
- .description("update project")
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
- program
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
- program
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
- program
446
+ mon
413
447
  .command("shell <service>")
414
- .description("open service shell")
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
- program
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
- // Instance management
429
- program
430
- .command("list-instances")
431
- .description("list instances")
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 instances configured");
478
+ console.log("No monitoring targets configured");
444
479
  console.log("");
445
- console.log("To add an instance:");
446
- console.log(" postgres-ai add-instance <connection-string> <name>");
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-instance 'postgresql://user:pass@host:5432/db' my-db");
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 placeholder
453
- const filtered = instances.filter((inst) => inst.name && inst.name !== "target-database");
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 instances configured");
490
+ console.log("No monitoring targets configured");
456
491
  console.log("");
457
- console.log("To add an instance:");
458
- console.log(" postgres-ai add-instance <connection-string> <name>");
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-instance 'postgresql://user:pass@host:5432/db' my-db");
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(`Instance: ${inst.name}`);
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
- program
475
- .command("add-instance [connStr] [name]")
476
- .description("add instance")
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(`Instance '${instanceName}' already exists`);
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(`Instance '${instanceName}' already exists`);
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(`Instance '${instanceName}' added`);
556
+ console.log(`Monitoring target '${instanceName}' added`);
522
557
  });
523
- program
524
- .command("remove-instance <name>")
525
- .description("remove instance")
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(`Instance '${name}' not found`);
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(`Instance '${name}' removed`);
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
- program
557
- .command("test-instance <name>")
558
- .description("test instance connectivity")
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(`Instance '${name}' not found`);
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 instance '${name}'`);
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
- const { stdout, stderr } = await execFilePromise("psql", [instance.conn_str, "-c", "SELECT version();", "--no-psqlrc"], { timeout: 10000, env: { ...process.env, PAGER: 'cat' } });
587
- console.log(`✓ Connection successful`);
588
- console.log(stdout.trim());
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 apiBaseUrl = (rootOpts.apiBaseUrl || process.env.PGAI_API_BASE_URL || "https://postgres.ai/api/general/").replace(/\/$/, "");
610
- const uiBaseUrl = (rootOpts.uiBaseUrl || process.env.PGAI_UI_BASE_URL || "https://console.postgres.ai").replace(/\/$/, "");
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, 300000);
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
- console.error(data);
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.exitCode = 1;
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 exchangeData = "";
689
- exchangeRes.on("data", (chunk) => (exchangeData += 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
- console.error(exchangeData);
694
- process.exitCode = 1;
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(exchangeData);
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.exitCode = 1;
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.exitCode = 1;
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
- console.error(`\nAuthentication failed: ${message}`);
729
- process.exitCode = 1;
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.exitCode = 1;
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.exitCode = 1;
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 mask = (k) => {
765
- if (k.length <= 8)
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
- program
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("\nRestart Grafana to apply:");
850
- console.log(" postgres-ai restart grafana");
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
- program
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