postgresai 0.11.0-alpha.11 → 0.11.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 +39 -0
- package/bin/postgres-ai.ts +119 -30
- package/dist/bin/postgres-ai.js +109 -30
- 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/README.md
CHANGED
|
@@ -88,6 +88,31 @@ postgres-ai mon check # System readiness check
|
|
|
88
88
|
postgres-ai mon shell <service> # Open shell to monitoring service
|
|
89
89
|
```
|
|
90
90
|
|
|
91
|
+
### MCP server (`mcp` group)
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
pgai mcp start # Start MCP stdio server exposing list_issues tool
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Cursor configuration example (Settings → MCP):
|
|
98
|
+
|
|
99
|
+
```json
|
|
100
|
+
{
|
|
101
|
+
"mcpServers": {
|
|
102
|
+
"PostgresAI": {
|
|
103
|
+
"command": "pgai",
|
|
104
|
+
"args": ["mcp", "start"],
|
|
105
|
+
"env": {
|
|
106
|
+
"PGAI_API_BASE_URL": "https://postgres.ai/api/general/"
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Tools exposed:
|
|
114
|
+
- list_issues: returns the same JSON as `pgai issues list`.
|
|
115
|
+
|
|
91
116
|
#### Grafana management
|
|
92
117
|
```bash
|
|
93
118
|
postgres-ai mon generate-grafana-password # Generate new Grafana password
|
|
@@ -117,6 +142,20 @@ API key resolution order:
|
|
|
117
142
|
3. User config file (`~/.config/postgresai/config.json`)
|
|
118
143
|
4. Legacy project config (`.pgwatch-config`)
|
|
119
144
|
|
|
145
|
+
Base URL resolution order:
|
|
146
|
+
- API base URL (`apiBaseUrl`):
|
|
147
|
+
1. Command line option (`--api-base-url`)
|
|
148
|
+
2. Environment variable (`PGAI_API_BASE_URL`)
|
|
149
|
+
3. User config file `baseUrl` (`~/.config/postgresai/config.json`)
|
|
150
|
+
4. Default: `https://postgres.ai/api/general/`
|
|
151
|
+
- UI base URL (`uiBaseUrl`):
|
|
152
|
+
1. Command line option (`--ui-base-url`)
|
|
153
|
+
2. Environment variable (`PGAI_UI_BASE_URL`)
|
|
154
|
+
3. Default: `https://console.postgres.ai`
|
|
155
|
+
|
|
156
|
+
Normalization:
|
|
157
|
+
- A single trailing `/` is removed to ensure consistent path joining.
|
|
158
|
+
|
|
120
159
|
### Environment variables
|
|
121
160
|
|
|
122
161
|
- `PGAI_API_KEY` - API key for PostgresAI services
|
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);
|
|
@@ -672,9 +675,8 @@ program
|
|
|
672
675
|
const params = pkce.generatePKCEParams();
|
|
673
676
|
|
|
674
677
|
const rootOpts = program.opts<CliOptions>();
|
|
675
|
-
|
|
676
|
-
const
|
|
677
|
-
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);
|
|
678
680
|
|
|
679
681
|
if (opts.debug) {
|
|
680
682
|
console.log(`Debug: Resolved API base URL: ${apiBaseUrl}`);
|
|
@@ -685,7 +687,7 @@ program
|
|
|
685
687
|
// Step 1: Start local callback server FIRST to get actual port
|
|
686
688
|
console.log("Starting local callback server...");
|
|
687
689
|
const requestedPort = opts.port || 0; // 0 = OS assigns available port
|
|
688
|
-
const callbackServer = authServer.createCallbackServer(requestedPort, params.state,
|
|
690
|
+
const callbackServer = authServer.createCallbackServer(requestedPort, params.state, 120000); // 2 minute timeout
|
|
689
691
|
|
|
690
692
|
// Wait a bit for server to start and get port
|
|
691
693
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
@@ -727,10 +729,20 @@ program
|
|
|
727
729
|
res.on("end", async () => {
|
|
728
730
|
if (res.statusCode !== 200) {
|
|
729
731
|
console.error(`Failed to initialize auth session: ${res.statusCode}`);
|
|
730
|
-
|
|
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
|
+
|
|
731
744
|
callbackServer.server.close();
|
|
732
|
-
process.
|
|
733
|
-
return;
|
|
745
|
+
process.exit(1);
|
|
734
746
|
}
|
|
735
747
|
|
|
736
748
|
// Step 3: Open browser
|
|
@@ -751,9 +763,22 @@ program
|
|
|
751
763
|
|
|
752
764
|
// Step 4: Wait for callback
|
|
753
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
|
+
|
|
754
776
|
try {
|
|
755
777
|
const { code } = await callbackServer.promise;
|
|
756
778
|
|
|
779
|
+
// Remove the cancel handler after successful auth
|
|
780
|
+
process.off("SIGINT", cancelHandler);
|
|
781
|
+
|
|
757
782
|
// Step 5: Exchange code for token
|
|
758
783
|
console.log("\nExchanging authorization code for API token...");
|
|
759
784
|
const exchangeData = JSON.stringify({
|
|
@@ -761,7 +786,6 @@ program
|
|
|
761
786
|
code_verifier: params.codeVerifier,
|
|
762
787
|
state: params.state,
|
|
763
788
|
});
|
|
764
|
-
|
|
765
789
|
const exchangeUrl = new URL(`${apiBaseUrl}/rpc/oauth_token_exchange`);
|
|
766
790
|
const exchangeReq = http.request(
|
|
767
791
|
exchangeUrl,
|
|
@@ -773,20 +797,31 @@ program
|
|
|
773
797
|
},
|
|
774
798
|
},
|
|
775
799
|
(exchangeRes) => {
|
|
776
|
-
let
|
|
777
|
-
exchangeRes.on("data", (chunk) => (
|
|
800
|
+
let exchangeBody = "";
|
|
801
|
+
exchangeRes.on("data", (chunk) => (exchangeBody += chunk));
|
|
778
802
|
exchangeRes.on("end", () => {
|
|
779
803
|
if (exchangeRes.statusCode !== 200) {
|
|
780
804
|
console.error(`Failed to exchange code for token: ${exchangeRes.statusCode}`);
|
|
781
|
-
|
|
782
|
-
|
|
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);
|
|
783
818
|
return;
|
|
784
819
|
}
|
|
785
820
|
|
|
786
821
|
try {
|
|
787
|
-
const result = JSON.parse(
|
|
788
|
-
const apiToken = result.api_token;
|
|
789
|
-
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.
|
|
790
825
|
|
|
791
826
|
// Step 6: Save token to config
|
|
792
827
|
config.writeConfig({
|
|
@@ -799,10 +834,11 @@ program
|
|
|
799
834
|
console.log(`API key saved to: ${config.getConfigPath()}`);
|
|
800
835
|
console.log(`Organization ID: ${orgId}`);
|
|
801
836
|
console.log(`\nYou can now use the CLI without specifying an API key.`);
|
|
837
|
+
process.exit(0);
|
|
802
838
|
} catch (err) {
|
|
803
839
|
const message = err instanceof Error ? err.message : String(err);
|
|
804
840
|
console.error(`Failed to parse response: ${message}`);
|
|
805
|
-
process.
|
|
841
|
+
process.exit(1);
|
|
806
842
|
}
|
|
807
843
|
});
|
|
808
844
|
}
|
|
@@ -810,16 +846,28 @@ program
|
|
|
810
846
|
|
|
811
847
|
exchangeReq.on("error", (err: Error) => {
|
|
812
848
|
console.error(`Exchange request failed: ${err.message}`);
|
|
813
|
-
process.
|
|
849
|
+
process.exit(1);
|
|
814
850
|
});
|
|
815
851
|
|
|
816
852
|
exchangeReq.write(exchangeData);
|
|
817
853
|
exchangeReq.end();
|
|
818
854
|
|
|
819
855
|
} catch (err) {
|
|
856
|
+
// Remove the cancel handler in error case too
|
|
857
|
+
process.off("SIGINT", cancelHandler);
|
|
858
|
+
|
|
820
859
|
const message = err instanceof Error ? err.message : String(err);
|
|
821
|
-
|
|
822
|
-
|
|
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);
|
|
823
871
|
}
|
|
824
872
|
});
|
|
825
873
|
}
|
|
@@ -828,7 +876,7 @@ program
|
|
|
828
876
|
initReq.on("error", (err: Error) => {
|
|
829
877
|
console.error(`Failed to connect to API: ${err.message}`);
|
|
830
878
|
callbackServer.server.close();
|
|
831
|
-
process.
|
|
879
|
+
process.exit(1);
|
|
832
880
|
});
|
|
833
881
|
|
|
834
882
|
initReq.write(initData);
|
|
@@ -837,7 +885,7 @@ program
|
|
|
837
885
|
} catch (err) {
|
|
838
886
|
const message = err instanceof Error ? err.message : String(err);
|
|
839
887
|
console.error(`Authentication error: ${message}`);
|
|
840
|
-
process.
|
|
888
|
+
process.exit(1);
|
|
841
889
|
}
|
|
842
890
|
});
|
|
843
891
|
|
|
@@ -859,13 +907,8 @@ program
|
|
|
859
907
|
console.log(`\nTo authenticate, run: pgai auth`);
|
|
860
908
|
return;
|
|
861
909
|
}
|
|
862
|
-
const
|
|
863
|
-
|
|
864
|
-
if (k.length <= 16) return `${k.slice(0, 4)}${"*".repeat(k.length - 8)}${k.slice(-4)}`;
|
|
865
|
-
// For longer keys, show more of the beginning to help identify them
|
|
866
|
-
return `${k.slice(0, Math.min(12, k.length - 8))}${"*".repeat(Math.max(4, k.length - 16))}${k.slice(-4)}`;
|
|
867
|
-
};
|
|
868
|
-
console.log(`Current API key: ${mask(cfg.apiKey)}`);
|
|
910
|
+
const { maskSecret } = require("../lib/util");
|
|
911
|
+
console.log(`Current API key: ${maskSecret(cfg.apiKey)}`);
|
|
869
912
|
if (cfg.orgId) {
|
|
870
913
|
console.log(`Organization ID: ${cfg.orgId}`);
|
|
871
914
|
}
|
|
@@ -953,8 +996,8 @@ mon
|
|
|
953
996
|
console.log(" URL: http://localhost:3000");
|
|
954
997
|
console.log(" Username: monitor");
|
|
955
998
|
console.log(` Password: ${newPassword}`);
|
|
956
|
-
console.log("\
|
|
957
|
-
console.log(" postgres-ai mon
|
|
999
|
+
console.log("\nReset Grafana to apply new password:");
|
|
1000
|
+
console.log(" postgres-ai mon reset grafana");
|
|
958
1001
|
} catch (error) {
|
|
959
1002
|
const message = error instanceof Error ? error.message : String(error);
|
|
960
1003
|
console.error(`Failed to generate password: ${message}`);
|
|
@@ -1002,5 +1045,51 @@ mon
|
|
|
1002
1045
|
console.log("");
|
|
1003
1046
|
});
|
|
1004
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
|
+
|
|
1005
1094
|
program.parseAsync(process.argv);
|
|
1006
1095
|
|
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
|
/**
|
|
@@ -602,8 +605,8 @@ program
|
|
|
602
605
|
// Generate PKCE parameters
|
|
603
606
|
const params = pkce.generatePKCEParams();
|
|
604
607
|
const rootOpts = program.opts();
|
|
605
|
-
const
|
|
606
|
-
const
|
|
608
|
+
const cfg = config.readConfig();
|
|
609
|
+
const { apiBaseUrl, uiBaseUrl } = (0, util_2.resolveBaseUrls)(rootOpts, cfg);
|
|
607
610
|
if (opts.debug) {
|
|
608
611
|
console.log(`Debug: Resolved API base URL: ${apiBaseUrl}`);
|
|
609
612
|
console.log(`Debug: Resolved UI base URL: ${uiBaseUrl}`);
|
|
@@ -612,7 +615,7 @@ program
|
|
|
612
615
|
// Step 1: Start local callback server FIRST to get actual port
|
|
613
616
|
console.log("Starting local callback server...");
|
|
614
617
|
const requestedPort = opts.port || 0; // 0 = OS assigns available port
|
|
615
|
-
const callbackServer = authServer.createCallbackServer(requestedPort, params.state,
|
|
618
|
+
const callbackServer = authServer.createCallbackServer(requestedPort, params.state, 120000); // 2 minute timeout
|
|
616
619
|
// Wait a bit for server to start and get port
|
|
617
620
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
618
621
|
const actualPort = callbackServer.getPort();
|
|
@@ -645,10 +648,19 @@ program
|
|
|
645
648
|
res.on("end", async () => {
|
|
646
649
|
if (res.statusCode !== 200) {
|
|
647
650
|
console.error(`Failed to initialize auth session: ${res.statusCode}`);
|
|
648
|
-
|
|
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
|
+
}
|
|
649
662
|
callbackServer.server.close();
|
|
650
|
-
process.
|
|
651
|
-
return;
|
|
663
|
+
process.exit(1);
|
|
652
664
|
}
|
|
653
665
|
// Step 3: Open browser
|
|
654
666
|
const authUrl = `${uiBaseUrl}/cli/auth?state=${encodeURIComponent(params.state)}&code_challenge=${encodeURIComponent(params.codeChallenge)}&code_challenge_method=S256&redirect_uri=${encodeURIComponent(redirectUri)}`;
|
|
@@ -664,8 +676,18 @@ program
|
|
|
664
676
|
(0, child_process_1.spawn)(openCommand, [authUrl], { detached: true, stdio: "ignore" }).unref();
|
|
665
677
|
// Step 4: Wait for callback
|
|
666
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);
|
|
667
687
|
try {
|
|
668
688
|
const { code } = await callbackServer.promise;
|
|
689
|
+
// Remove the cancel handler after successful auth
|
|
690
|
+
process.off("SIGINT", cancelHandler);
|
|
669
691
|
// Step 5: Exchange code for token
|
|
670
692
|
console.log("\nExchanging authorization code for API token...");
|
|
671
693
|
const exchangeData = JSON.stringify({
|
|
@@ -681,19 +703,29 @@ program
|
|
|
681
703
|
"Content-Length": Buffer.byteLength(exchangeData),
|
|
682
704
|
},
|
|
683
705
|
}, (exchangeRes) => {
|
|
684
|
-
let
|
|
685
|
-
exchangeRes.on("data", (chunk) => (
|
|
706
|
+
let exchangeBody = "";
|
|
707
|
+
exchangeRes.on("data", (chunk) => (exchangeBody += chunk));
|
|
686
708
|
exchangeRes.on("end", () => {
|
|
687
709
|
if (exchangeRes.statusCode !== 200) {
|
|
688
710
|
console.error(`Failed to exchange code for token: ${exchangeRes.statusCode}`);
|
|
689
|
-
|
|
690
|
-
|
|
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);
|
|
691
723
|
return;
|
|
692
724
|
}
|
|
693
725
|
try {
|
|
694
|
-
const result = JSON.parse(
|
|
695
|
-
const apiToken = result.api_token;
|
|
696
|
-
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.
|
|
697
729
|
// Step 6: Save token to config
|
|
698
730
|
config.writeConfig({
|
|
699
731
|
apiKey: apiToken,
|
|
@@ -704,32 +736,43 @@ program
|
|
|
704
736
|
console.log(`API key saved to: ${config.getConfigPath()}`);
|
|
705
737
|
console.log(`Organization ID: ${orgId}`);
|
|
706
738
|
console.log(`\nYou can now use the CLI without specifying an API key.`);
|
|
739
|
+
process.exit(0);
|
|
707
740
|
}
|
|
708
741
|
catch (err) {
|
|
709
742
|
const message = err instanceof Error ? err.message : String(err);
|
|
710
743
|
console.error(`Failed to parse response: ${message}`);
|
|
711
|
-
process.
|
|
744
|
+
process.exit(1);
|
|
712
745
|
}
|
|
713
746
|
});
|
|
714
747
|
});
|
|
715
748
|
exchangeReq.on("error", (err) => {
|
|
716
749
|
console.error(`Exchange request failed: ${err.message}`);
|
|
717
|
-
process.
|
|
750
|
+
process.exit(1);
|
|
718
751
|
});
|
|
719
752
|
exchangeReq.write(exchangeData);
|
|
720
753
|
exchangeReq.end();
|
|
721
754
|
}
|
|
722
755
|
catch (err) {
|
|
756
|
+
// Remove the cancel handler in error case too
|
|
757
|
+
process.off("SIGINT", cancelHandler);
|
|
723
758
|
const message = err instanceof Error ? err.message : String(err);
|
|
724
|
-
|
|
725
|
-
|
|
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);
|
|
726
769
|
}
|
|
727
770
|
});
|
|
728
771
|
});
|
|
729
772
|
initReq.on("error", (err) => {
|
|
730
773
|
console.error(`Failed to connect to API: ${err.message}`);
|
|
731
774
|
callbackServer.server.close();
|
|
732
|
-
process.
|
|
775
|
+
process.exit(1);
|
|
733
776
|
});
|
|
734
777
|
initReq.write(initData);
|
|
735
778
|
initReq.end();
|
|
@@ -737,7 +780,7 @@ program
|
|
|
737
780
|
catch (err) {
|
|
738
781
|
const message = err instanceof Error ? err.message : String(err);
|
|
739
782
|
console.error(`Authentication error: ${message}`);
|
|
740
|
-
process.
|
|
783
|
+
process.exit(1);
|
|
741
784
|
}
|
|
742
785
|
});
|
|
743
786
|
program
|
|
@@ -757,15 +800,8 @@ program
|
|
|
757
800
|
console.log(`\nTo authenticate, run: pgai auth`);
|
|
758
801
|
return;
|
|
759
802
|
}
|
|
760
|
-
const
|
|
761
|
-
|
|
762
|
-
return "****";
|
|
763
|
-
if (k.length <= 16)
|
|
764
|
-
return `${k.slice(0, 4)}${"*".repeat(k.length - 8)}${k.slice(-4)}`;
|
|
765
|
-
// For longer keys, show more of the beginning to help identify them
|
|
766
|
-
return `${k.slice(0, Math.min(12, k.length - 8))}${"*".repeat(Math.max(4, k.length - 16))}${k.slice(-4)}`;
|
|
767
|
-
};
|
|
768
|
-
console.log(`Current API key: ${mask(cfg.apiKey)}`);
|
|
803
|
+
const { maskSecret } = require("../lib/util");
|
|
804
|
+
console.log(`Current API key: ${maskSecret(cfg.apiKey)}`);
|
|
769
805
|
if (cfg.orgId) {
|
|
770
806
|
console.log(`Organization ID: ${cfg.orgId}`);
|
|
771
807
|
}
|
|
@@ -842,8 +878,8 @@ mon
|
|
|
842
878
|
console.log(" URL: http://localhost:3000");
|
|
843
879
|
console.log(" Username: monitor");
|
|
844
880
|
console.log(` Password: ${newPassword}`);
|
|
845
|
-
console.log("\
|
|
846
|
-
console.log(" postgres-ai mon
|
|
881
|
+
console.log("\nReset Grafana to apply new password:");
|
|
882
|
+
console.log(" postgres-ai mon reset grafana");
|
|
847
883
|
}
|
|
848
884
|
catch (error) {
|
|
849
885
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -889,5 +925,48 @@ mon
|
|
|
889
925
|
console.log(` Password: ${password}`);
|
|
890
926
|
console.log("");
|
|
891
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
|
+
});
|
|
892
971
|
program.parseAsync(process.argv);
|
|
893
972
|
//# sourceMappingURL=postgres-ai.js.map
|