postgresai 0.14.0-dev.51 → 0.14.0-dev.53

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.
@@ -7,7 +7,6 @@ import * as yaml from "js-yaml";
7
7
  import * as fs from "fs";
8
8
  import * as path from "path";
9
9
  import * as os from "os";
10
- import * as crypto from "node:crypto";
11
10
  import { Client } from "pg";
12
11
  import { startMcpServer } from "../lib/mcp-server";
13
12
  import { fetchIssues, fetchIssueComments, createIssueComment, fetchIssue } from "../lib/issues";
@@ -18,8 +17,6 @@ import * as authServer from "../lib/auth-server";
18
17
  import { maskSecret } from "../lib/util";
19
18
  import { createInterface } from "readline";
20
19
  import * as childProcess from "child_process";
21
- import { REPORT_GENERATORS, CHECK_INFO, generateAllReports } from "../lib/checkup";
22
- import { createCheckupReport, uploadCheckupReportJson, RpcError, formatRpcErrorForDisplay } from "../lib/checkup-api";
23
20
 
24
21
  // Singleton readline interface for stdin prompts
25
22
  let rl: ReturnType<typeof createInterface> | null = null;
@@ -112,62 +109,6 @@ async function question(prompt: string): Promise<string> {
112
109
  });
113
110
  }
114
111
 
115
- function expandHomePath(p: string): string {
116
- const s = (p || "").trim();
117
- if (!s) return s;
118
- if (s === "~") return os.homedir();
119
- if (s.startsWith("~/") || s.startsWith("~\\")) {
120
- return path.join(os.homedir(), s.slice(2));
121
- }
122
- return s;
123
- }
124
-
125
- function createTtySpinner(
126
- enabled: boolean,
127
- initialText: string
128
- ): { update: (text: string) => void; stop: (finalText?: string) => void } {
129
- if (!enabled) {
130
- return {
131
- update: () => {},
132
- stop: () => {},
133
- };
134
- }
135
-
136
- const frames = ["|", "/", "-", "\\"];
137
- const startTs = Date.now();
138
- let text = initialText;
139
- let frameIdx = 0;
140
- let stopped = false;
141
-
142
- const render = (): void => {
143
- if (stopped) return;
144
- const elapsedSec = ((Date.now() - startTs) / 1000).toFixed(1);
145
- const frame = frames[frameIdx % frames.length]!;
146
- frameIdx += 1;
147
- process.stdout.write(`\r\x1b[2K${frame} ${text} (${elapsedSec}s)`);
148
- };
149
-
150
- const timer = setInterval(render, 120);
151
- render(); // immediate feedback
152
-
153
- return {
154
- update: (t: string) => {
155
- text = t;
156
- render();
157
- },
158
- stop: (finalText?: string) => {
159
- if (stopped) return;
160
- stopped = true;
161
- clearInterval(timer);
162
- process.stdout.write("\r\x1b[2K");
163
- if (finalText && finalText.trim()) {
164
- process.stdout.write(finalText);
165
- }
166
- process.stdout.write("\n");
167
- },
168
- };
169
- }
170
-
171
112
  /**
172
113
  * CLI configuration options
173
114
  */
@@ -345,20 +286,6 @@ program
345
286
  "UI base URL for browser routes (overrides PGAI_UI_BASE_URL)"
346
287
  );
347
288
 
348
- program
349
- .command("set-default-project <project>")
350
- .description("store default project for checkup uploads")
351
- .action(async (project: string) => {
352
- const value = (project || "").trim();
353
- if (!value) {
354
- console.error("Error: project is required");
355
- process.exitCode = 1;
356
- return;
357
- }
358
- config.writeConfig({ defaultProject: value });
359
- console.log(`Default project saved: ${value}`);
360
- });
361
-
362
289
  program
363
290
  .command("prepare-db [conn]")
364
291
  .description("prepare database for monitoring: create monitoring user, required view(s), and grant permissions (idempotent)")
@@ -686,251 +613,6 @@ program
686
613
  }
687
614
  });
688
615
 
689
- program
690
- .command("checkup [conn]")
691
- .description("generate health check reports directly from PostgreSQL (express mode)")
692
- .option("--check-id <id>", `specific check to run: ${Object.keys(CHECK_INFO).join(", ")}, or ALL`, "ALL")
693
- .option("--node-name <name>", "node name for reports", "node-01")
694
- .option("--output <path>", "output directory for JSON files")
695
- .option("--[no-]upload", "upload JSON results to PostgresAI (default: enabled; requires API key)", undefined)
696
- .option(
697
- "--project <project>",
698
- "project name or ID for remote upload (used with --upload; defaults to config defaultProject; auto-generated on first run)"
699
- )
700
- .option("--json", "output JSON to stdout (implies --no-upload)")
701
- .addHelpText(
702
- "after",
703
- [
704
- "",
705
- "Available checks:",
706
- ...Object.entries(CHECK_INFO).map(([id, title]) => ` ${id}: ${title}`),
707
- "",
708
- "Examples:",
709
- " postgresai checkup postgresql://user:pass@host:5432/db",
710
- " postgresai checkup postgresql://user:pass@host:5432/db --check-id A003",
711
- " postgresai checkup postgresql://user:pass@host:5432/db --output ./reports",
712
- " postgresai checkup postgresql://user:pass@host:5432/db --project my_project",
713
- " postgresai set-default-project my_project",
714
- " postgresai checkup postgresql://user:pass@host:5432/db",
715
- " postgresai checkup postgresql://user:pass@host:5432/db --no-upload --json",
716
- ].join("\n")
717
- )
718
- .action(async (conn: string | undefined, opts: {
719
- checkId: string;
720
- nodeName: string;
721
- output?: string;
722
- upload?: boolean;
723
- project?: string;
724
- json?: boolean;
725
- }, cmd: Command) => {
726
- if (!conn) {
727
- // No args — show help like other commands do (instead of a bare error).
728
- cmd.outputHelp();
729
- process.exitCode = 1;
730
- return;
731
- }
732
-
733
- const shouldPrintJson = !!opts.json;
734
- // `--json` implies "local output" mode — do not upload by default.
735
- const shouldUpload = opts.upload !== false && !shouldPrintJson;
736
- const generateDefaultProjectName = (): string => {
737
- // Must start with a letter; use only letters/numbers/underscores.
738
- return `project_${crypto.randomBytes(6).toString("hex")}`;
739
- };
740
-
741
- // Preflight: validate/create output directory BEFORE connecting / running checks.
742
- // This avoids waiting on network/DB work only to fail at the very end.
743
- let outputPath: string | undefined;
744
- if (opts.output) {
745
- const outputDir = expandHomePath(opts.output);
746
- outputPath = path.isAbsolute(outputDir) ? outputDir : path.resolve(process.cwd(), outputDir);
747
- if (!fs.existsSync(outputPath)) {
748
- try {
749
- fs.mkdirSync(outputPath, { recursive: true });
750
- } catch (e) {
751
- const errAny = e as any;
752
- const code = typeof errAny?.code === "string" ? errAny.code : "";
753
- const msg = errAny instanceof Error ? errAny.message : String(errAny);
754
- if (code === "EACCES" || code === "EPERM" || code === "ENOENT") {
755
- console.error(`Error: Failed to create output directory: ${outputPath}`);
756
- console.error(`Reason: ${msg}`);
757
- console.error("Tip: choose a writable path, e.g. --output ./reports or --output ~/reports");
758
- process.exitCode = 1;
759
- return;
760
- }
761
- throw e;
762
- }
763
- }
764
- }
765
-
766
- // Preflight: validate upload flags/credentials BEFORE connecting / running checks.
767
- // This allows "fast-fail" for missing API key / project name.
768
- let uploadCfg:
769
- | { apiKey: string; apiBaseUrl: string; project: string; epoch: number }
770
- | undefined;
771
- let projectWasGenerated = false;
772
- if (shouldUpload) {
773
- const rootOpts = program.opts() as CliOptions;
774
- const { apiKey } = getConfig(rootOpts);
775
- if (!apiKey) {
776
- console.error("Error: API key is required for upload");
777
- console.error("Tip: run 'postgresai auth' or pass --api-key / set PGAI_API_KEY");
778
- process.exitCode = 1;
779
- return;
780
- }
781
-
782
- const cfg = config.readConfig();
783
- const { apiBaseUrl } = resolveBaseUrls(rootOpts, cfg);
784
- let project = ((opts.project || cfg.defaultProject) || "").trim();
785
- if (!project) {
786
- project = generateDefaultProjectName();
787
- projectWasGenerated = true;
788
- try {
789
- config.writeConfig({ defaultProject: project });
790
- } catch (e) {
791
- const message = e instanceof Error ? e.message : String(e);
792
- console.error(`Warning: Failed to save generated default project: ${message}`);
793
- }
794
- }
795
- uploadCfg = {
796
- apiKey,
797
- apiBaseUrl,
798
- project,
799
- };
800
- }
801
-
802
- // Use the same SSL behavior as prepare-db:
803
- // - Default: sslmode=prefer (try SSL first, fallback to non-SSL)
804
- // - Respect PGSSLMODE env and ?sslmode=... in connection URI
805
- const adminConn = resolveAdminConnection({
806
- conn,
807
- envPassword: process.env.PGPASSWORD,
808
- });
809
- let client: Client | undefined;
810
- const spinnerEnabled = !!process.stdout.isTTY && !!shouldUpload;
811
- const spinner = createTtySpinner(spinnerEnabled, "Connecting to Postgres");
812
-
813
- try {
814
- spinner.update("Connecting to Postgres");
815
- const connResult = await connectWithSslFallback(Client, adminConn);
816
- client = connResult.client as Client;
817
-
818
- let reports: Record<string, any>;
819
- let uploadSummary:
820
- | { project: string; reportId: number; uploaded: Array<{ checkId: string; filename: string; chunkId: number }> }
821
- | undefined;
822
-
823
- if (opts.checkId === "ALL") {
824
- reports = await generateAllReports(client, opts.nodeName, (p) => {
825
- spinner.update(`Running ${p.checkId}: ${p.checkTitle} (${p.index}/${p.total})`);
826
- });
827
- } else {
828
- const checkId = opts.checkId.toUpperCase();
829
- const generator = REPORT_GENERATORS[checkId];
830
- if (!generator) {
831
- spinner.stop();
832
- console.error(`Unknown check ID: ${opts.checkId}`);
833
- console.error(`Available: ${Object.keys(CHECK_INFO).join(", ")}, ALL`);
834
- process.exitCode = 1;
835
- return;
836
- }
837
- spinner.update(`Running ${checkId}: ${CHECK_INFO[checkId] || checkId}`);
838
- reports = { [checkId]: await generator(client, opts.nodeName) };
839
- }
840
-
841
- // Optional: upload to PostgresAI API.
842
- if (uploadCfg) {
843
- spinner.update("Creating remote checkup report");
844
- const created = await createCheckupReport({
845
- apiKey: uploadCfg.apiKey,
846
- apiBaseUrl: uploadCfg.apiBaseUrl,
847
- project: uploadCfg.project,
848
- });
849
-
850
- const reportId = created.reportId;
851
- // Keep upload progress out of stdout when JSON is printed to stdout.
852
- const logUpload = (msg: string): void => {
853
- if (shouldPrintJson) {
854
- console.error(msg);
855
- } else {
856
- console.log(msg);
857
- }
858
- };
859
- logUpload(`Created remote checkup report: ${reportId}`);
860
-
861
- const uploaded: Array<{ checkId: string; filename: string; chunkId: number }> = [];
862
- for (const [checkId, report] of Object.entries(reports)) {
863
- spinner.update(`Uploading ${checkId}.json`);
864
- const jsonText = JSON.stringify(report, null, 2);
865
- const r = await uploadCheckupReportJson({
866
- apiKey: uploadCfg.apiKey,
867
- apiBaseUrl: uploadCfg.apiBaseUrl,
868
- reportId,
869
- filename: `${checkId}.json`,
870
- checkId,
871
- jsonText,
872
- });
873
- uploaded.push({ checkId, filename: `${checkId}.json`, chunkId: r.reportChunkId });
874
- }
875
- logUpload("Upload completed");
876
- uploadSummary = { project: uploadCfg.project, reportId, uploaded };
877
- }
878
-
879
- spinner.stop();
880
- // Output results
881
- if (opts.output) {
882
- // Write to files
883
- // outputPath is preflight-validated above
884
- const outDir = outputPath || path.resolve(process.cwd(), expandHomePath(opts.output));
885
- for (const [checkId, report] of Object.entries(reports)) {
886
- const filePath = path.join(outDir, `${checkId}.json`);
887
- fs.writeFileSync(filePath, JSON.stringify(report, null, 2), "utf8");
888
- console.log(`✓ ${checkId}: ${filePath}`);
889
- }
890
- }
891
-
892
- if (uploadSummary) {
893
- const out = shouldPrintJson ? console.error : console.log;
894
- out("\nCheckup report uploaded");
895
- out("======================\n");
896
- if (projectWasGenerated) {
897
- out(`Project: ${uploadSummary.project} (generated and saved as default)`);
898
- } else {
899
- out(`Project: ${uploadSummary.project}`);
900
- }
901
- out(`Report ID: ${uploadSummary.reportId}`);
902
- out("View in Console: console.postgres.ai → Support → checkup reports");
903
- out("");
904
- out("Files:");
905
- for (const item of uploadSummary.uploaded) {
906
- out(`- ${item.checkId}: ${item.filename}`);
907
- }
908
- }
909
-
910
- if (shouldPrintJson) {
911
- console.log(JSON.stringify(reports, null, 2));
912
- } else if (!shouldUpload && !opts.output) {
913
- // Offline mode keeps historical behavior: print JSON when not uploading and no --output.
914
- console.log(JSON.stringify(reports, null, 2));
915
- }
916
- } catch (error) {
917
- spinner.stop();
918
- if (error instanceof RpcError) {
919
- for (const line of formatRpcErrorForDisplay(error)) {
920
- console.error(line);
921
- }
922
- } else {
923
- const message = error instanceof Error ? error.message : String(error);
924
- console.error(`Error: ${message}`);
925
- }
926
- process.exitCode = 1;
927
- } finally {
928
- if (client) {
929
- await client.end();
930
- }
931
- }
932
- });
933
-
934
616
  /**
935
617
  * Stub function for not implemented commands
936
618
  */
@@ -1915,9 +1597,6 @@ auth
1915
1597
  }
1916
1598
 
1917
1599
  config.writeConfig({ apiKey: trimmedKey });
1918
- // When API key is set directly, invalidate org/project selection
1919
- // as it likely belongs to a different account/session.
1920
- config.deleteConfigKeys(["orgId", "defaultProject"]);
1921
1600
  console.log(`API key saved to ${config.getConfigPath()}`);
1922
1601
  return;
1923
1602
  }
@@ -2096,9 +1775,6 @@ auth
2096
1775
  baseUrl: apiBaseUrl,
2097
1776
  orgId: orgId,
2098
1777
  });
2099
- // When re-authing via OAuth, orgId will be refreshed,
2100
- // but defaultProject may no longer be valid in the new org.
2101
- config.deleteConfigKeys(["defaultProject"]);
2102
1778
 
2103
1779
  console.log("\nAuthentication successful!");
2104
1780
  console.log(`API key saved to: ${config.getConfigPath()}`);
package/bun.lock CHANGED
@@ -14,8 +14,6 @@
14
14
  "@types/bun": "^1.1.14",
15
15
  "@types/js-yaml": "^4.0.9",
16
16
  "@types/pg": "^8.15.6",
17
- "ajv": "^8.17.1",
18
- "ajv-formats": "^3.0.1",
19
17
  "typescript": "^5.3.3",
20
18
  },
21
19
  },
@@ -131,7 +129,7 @@
131
129
 
132
130
  "jose": ["jose@6.1.3", "", {}, "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ=="],
133
131
 
134
- "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
132
+ "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": "bin/js-yaml.js" }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
135
133
 
136
134
  "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
137
135