postgresai 0.16.0-rc.4 → 0.16.0

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.
Files changed (85) hide show
  1. package/README.md +154 -0
  2. package/dist/bin/postgres-ai.js +2911 -255
  3. package/package.json +12 -3
  4. package/schemas/A002.schema.json +63 -0
  5. package/schemas/A003.schema.json +73 -0
  6. package/schemas/A004.schema.json +81 -0
  7. package/schemas/A007.schema.json +71 -0
  8. package/schemas/A013.schema.json +61 -0
  9. package/schemas/D001.schema.json +71 -0
  10. package/schemas/D004.schema.json +136 -0
  11. package/schemas/F001.schema.json +73 -0
  12. package/schemas/F002.schema.json +108 -0
  13. package/schemas/F003.schema.json +138 -0
  14. package/schemas/F004.schema.json +125 -0
  15. package/schemas/F005.schema.json +131 -0
  16. package/schemas/F009.schema.json +155 -0
  17. package/schemas/G001.schema.json +135 -0
  18. package/schemas/G003.schema.json +90 -0
  19. package/schemas/H001.schema.json +141 -0
  20. package/schemas/H002.schema.json +129 -0
  21. package/schemas/H004.schema.json +128 -0
  22. package/schemas/I001.schema.json +149 -0
  23. package/schemas/K001.schema.json +161 -0
  24. package/schemas/K003.schema.json +163 -0
  25. package/schemas/K004.schema.json +110 -0
  26. package/schemas/K005.schema.json +110 -0
  27. package/schemas/K006.schema.json +110 -0
  28. package/schemas/K007.schema.json +110 -0
  29. package/schemas/K008.schema.json +110 -0
  30. package/schemas/M001.schema.json +119 -0
  31. package/schemas/M002.schema.json +110 -0
  32. package/schemas/M003.schema.json +128 -0
  33. package/schemas/N001.schema.json +161 -0
  34. package/schemas/query.schema.json +62 -0
  35. package/CHANGELOG.md +0 -11
  36. package/bin/postgres-ai.ts +0 -5578
  37. package/bun.lock +0 -258
  38. package/bunfig.toml +0 -20
  39. package/lib/aas-onboard.ts +0 -251
  40. package/lib/auth-server.ts +0 -285
  41. package/lib/checkup-api.ts +0 -526
  42. package/lib/checkup-dictionary.ts +0 -103
  43. package/lib/checkup-summary.ts +0 -338
  44. package/lib/checkup.ts +0 -2261
  45. package/lib/config.ts +0 -171
  46. package/lib/init.ts +0 -1152
  47. package/lib/instances.ts +0 -245
  48. package/lib/issues.ts +0 -1060
  49. package/lib/mcp-server.ts +0 -667
  50. package/lib/metrics-loader.ts +0 -134
  51. package/lib/pkce.ts +0 -79
  52. package/lib/reports.ts +0 -373
  53. package/lib/storage.ts +0 -367
  54. package/lib/supabase.ts +0 -826
  55. package/lib/util.ts +0 -134
  56. package/packages/postgres-ai/README.md +0 -26
  57. package/packages/postgres-ai/bin/postgres-ai.js +0 -27
  58. package/packages/postgres-ai/package.json +0 -27
  59. package/scripts/embed-checkup-dictionary.ts +0 -115
  60. package/scripts/embed-metrics.ts +0 -160
  61. package/scripts/generate-release-notes.ts +0 -668
  62. package/test/PERMISSION_CHECK_TEST_SUMMARY.md +0 -139
  63. package/test/aas-onboard.test.ts +0 -301
  64. package/test/auth.test.ts +0 -287
  65. package/test/checkup.integration.test.ts +0 -413
  66. package/test/checkup.test.ts +0 -3626
  67. package/test/compose-cmd.test.ts +0 -120
  68. package/test/config-consistency.test.ts +0 -352
  69. package/test/init.integration.test.ts +0 -438
  70. package/test/init.test.ts +0 -1816
  71. package/test/issues.cli.test.ts +0 -1162
  72. package/test/issues.test.ts +0 -456
  73. package/test/mcp-server.test.ts +0 -2530
  74. package/test/monitoring.test.ts +0 -746
  75. package/test/permission-check-sql.test.ts +0 -116
  76. package/test/reports.cli.test.ts +0 -793
  77. package/test/reports.test.ts +0 -977
  78. package/test/schema-validation.test.ts +0 -231
  79. package/test/storage.test.ts +0 -935
  80. package/test/supabase.test.ts +0 -709
  81. package/test/targets-add-config.test.ts +0 -28
  82. package/test/test-utils.ts +0 -190
  83. package/test/upgrade.test.ts +0 -1056
  84. package/test/util.test.ts +0 -44
  85. package/tsconfig.json +0 -20
package/lib/util.ts DELETED
@@ -1,134 +0,0 @@
1
- /**
2
- * Map of HTTP status codes to human-friendly messages.
3
- */
4
- const HTTP_STATUS_MESSAGES: Record<number, string> = {
5
- 400: "Bad Request",
6
- 401: "Unauthorized - check your API key",
7
- 403: "Forbidden - access denied",
8
- 404: "Not Found",
9
- 408: "Request Timeout",
10
- 429: "Too Many Requests - rate limited",
11
- 500: "Internal Server Error",
12
- 502: "Bad Gateway - server temporarily unavailable",
13
- 503: "Service Unavailable - server temporarily unavailable",
14
- 504: "Gateway Timeout - server temporarily unavailable",
15
- };
16
-
17
- /**
18
- * Check if a string looks like HTML content.
19
- */
20
- function isHtmlContent(text: string): boolean {
21
- const trimmed = text.trim();
22
- return trimmed.startsWith("<!DOCTYPE") || trimmed.startsWith("<html") || trimmed.startsWith("<HTML");
23
- }
24
-
25
- /**
26
- * Remediation hint appended to 401 errors so both humans and AI agents
27
- * (MCP tool callers) know how to recover from an invalid/stale API key.
28
- */
29
- const AUTH_REMEDIATION_HINT = "Run 'postgresai auth' to (re)authenticate, or set/update PGAI_API_KEY.";
30
-
31
- /**
32
- * Format an HTTP error response into a clean, developer-friendly message.
33
- * Handles HTML error pages (e.g., from Cloudflare) by showing just the status code and message.
34
- * For 401 responses, appends a remediation hint pointing at `postgresai auth`.
35
- */
36
- export function formatHttpError(operation: string, status: number, responseBody?: string): string {
37
- const statusMessage = HTTP_STATUS_MESSAGES[status] || "Request failed";
38
- let errMsg = `${operation}: HTTP ${status} - ${statusMessage}`;
39
- const remediation = status === 401 ? `\n${AUTH_REMEDIATION_HINT}` : "";
40
-
41
- if (responseBody) {
42
- // If it's HTML (like Cloudflare error pages), don't dump the raw HTML
43
- if (isHtmlContent(responseBody)) {
44
- // Just use the status message, don't append HTML
45
- return errMsg + remediation;
46
- }
47
-
48
- // Try to parse as JSON for structured error info
49
- try {
50
- const errObj = JSON.parse(responseBody);
51
- // Extract common error message fields
52
- const message = errObj.message || errObj.error || errObj.detail;
53
- if (message && typeof message === "string") {
54
- errMsg += `\n${message}`;
55
- } else {
56
- errMsg += `\n${JSON.stringify(errObj, null, 2)}`;
57
- }
58
- } catch {
59
- // Plain text error - append it if it's short and useful
60
- const trimmed = responseBody.trim();
61
- if (trimmed.length > 0 && trimmed.length < 500) {
62
- errMsg += `\n${trimmed}`;
63
- }
64
- }
65
- }
66
-
67
- return errMsg + remediation;
68
- }
69
-
70
- export function maskSecret(secret: string): string {
71
- if (!secret) return "";
72
- if (secret.length <= 8) return "****";
73
- if (secret.length <= 16) return `${secret.slice(0, 4)}${"*".repeat(secret.length - 8)}${secret.slice(-4)}`;
74
- return `${secret.slice(0, Math.min(12, secret.length - 8))}${"*".repeat(Math.max(4, secret.length - 16))}${secret.slice(-4)}`;
75
- }
76
-
77
-
78
- export interface RootOptsLike {
79
- apiBaseUrl?: string;
80
- uiBaseUrl?: string;
81
- storageBaseUrl?: string;
82
- }
83
-
84
- export interface ConfigLike {
85
- baseUrl?: string | null;
86
- storageBaseUrl?: string | null;
87
- }
88
-
89
- export interface ResolvedBaseUrls {
90
- apiBaseUrl: string;
91
- uiBaseUrl: string;
92
- storageBaseUrl: string;
93
- }
94
-
95
- /**
96
- * Normalize a base URL by trimming a single trailing slash and validating.
97
- * @throws Error if the URL is invalid
98
- */
99
- export function normalizeBaseUrl(value: string): string {
100
- const trimmed = (value || "").replace(/\/$/, "");
101
- try {
102
- // Validate
103
- // eslint-disable-next-line no-new
104
- new URL(trimmed);
105
- } catch {
106
- throw new Error(`Invalid base URL: ${value}`);
107
- }
108
- return trimmed;
109
- }
110
-
111
- /**
112
- * Resolve API and UI base URLs using precedence and normalize them.
113
- * Precedence (API): opts.apiBaseUrl → env.PGAI_API_BASE_URL → cfg.baseUrl → default
114
- * Precedence (UI): opts.uiBaseUrl → env.PGAI_UI_BASE_URL → default
115
- */
116
- export function resolveBaseUrls(
117
- opts?: RootOptsLike,
118
- cfg?: ConfigLike,
119
- defaults: { apiBaseUrl?: string; uiBaseUrl?: string; storageBaseUrl?: string } = {}
120
- ): ResolvedBaseUrls {
121
- const defApi = defaults.apiBaseUrl || "https://postgres.ai/api/general/";
122
- const defUi = defaults.uiBaseUrl || "https://console.postgres.ai";
123
- const defStorage = defaults.storageBaseUrl || "https://postgres.ai/storage";
124
-
125
- const apiCandidate = (opts?.apiBaseUrl || process.env.PGAI_API_BASE_URL || cfg?.baseUrl || defApi) as string;
126
- const uiCandidate = (opts?.uiBaseUrl || process.env.PGAI_UI_BASE_URL || defUi) as string;
127
- const storageCandidate = (opts?.storageBaseUrl || process.env.PGAI_STORAGE_BASE_URL || cfg?.storageBaseUrl || defStorage) as string;
128
-
129
- return {
130
- apiBaseUrl: normalizeBaseUrl(apiCandidate),
131
- uiBaseUrl: normalizeBaseUrl(uiCandidate),
132
- storageBaseUrl: normalizeBaseUrl(storageCandidate),
133
- };
134
- }
@@ -1,26 +0,0 @@
1
- # postgres-ai
2
-
3
- This is a wrapper package for [postgresai](https://www.npmjs.com/package/postgresai).
4
-
5
- ## Prefer installing postgresai directly
6
-
7
- ```bash
8
- npm install -g postgresai
9
- ```
10
-
11
- This gives you two commands:
12
- - `postgresai` — canonical, discoverable
13
- - `pgai` — short and convenient
14
-
15
- ## Why this package exists
16
-
17
- This package exists for discoverability on npm. If you search for "postgres-ai", you'll find this package which depends on and forwards to `postgresai`.
18
-
19
- Installing this package (`npm install -g postgres-ai`) will install both packages, giving you all three command aliases:
20
- - `postgres-ai` (from this package)
21
- - `postgresai` (from the main package)
22
- - `pgai` (from the main package)
23
-
24
- ## Documentation
25
-
26
- See the main package for full documentation: https://www.npmjs.com/package/postgresai
@@ -1,27 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * postgres-ai wrapper - forwards all commands to postgresai CLI
4
- *
5
- * This package exists for discoverability. For direct installation,
6
- * prefer: npm install -g postgresai
7
- */
8
- const { spawn } = require('child_process');
9
-
10
- // Find postgresai binary from the dependency
11
- // Uses the "cli" export defined in postgresai's package.json
12
- const postgresaiBin = require.resolve('postgresai/cli');
13
-
14
- // Forward all arguments to postgresai
15
- const child = spawn(process.execPath, [postgresaiBin, ...process.argv.slice(2)], {
16
- stdio: 'inherit',
17
- env: process.env,
18
- });
19
-
20
- child.on('close', (code) => {
21
- process.exit(code ?? 0);
22
- });
23
-
24
- child.on('error', (err) => {
25
- console.error(`Failed to start postgresai: ${err.message}`);
26
- process.exit(1);
27
- });
@@ -1,27 +0,0 @@
1
- {
2
- "name": "postgres-ai",
3
- "version": "0.0.0-dev.0",
4
- "description": "PostgresAI CLI (wrapper package - prefer installing postgresai directly)",
5
- "license": "Apache-2.0",
6
- "private": false,
7
- "repository": {
8
- "type": "git",
9
- "url": "git+https://gitlab.com/postgres-ai/postgres_ai.git"
10
- },
11
- "homepage": "https://gitlab.com/postgres-ai/postgres_ai",
12
- "bugs": {
13
- "url": "https://gitlab.com/postgres-ai/postgres_ai/-/issues"
14
- },
15
- "bin": {
16
- "postgres-ai": "./bin/postgres-ai.js"
17
- },
18
- "engines": {
19
- "node": ">=18"
20
- },
21
- "dependencies": {
22
- "postgresai": ">=0.12.0"
23
- },
24
- "publishConfig": {
25
- "access": "public"
26
- }
27
- }
@@ -1,115 +0,0 @@
1
- #!/usr/bin/env bun
2
- /**
3
- * Build script to fetch checkup dictionary from API and embed it.
4
- *
5
- * This script fetches from https://postgres.ai/api/general/checkup_dictionary
6
- * and generates cli/lib/checkup-dictionary-embedded.ts with the data embedded.
7
- *
8
- * The generated file is NOT committed to git - it's regenerated at build time.
9
- *
10
- * Usage: bun run scripts/embed-checkup-dictionary.ts
11
- */
12
-
13
- import * as fs from "fs";
14
- import * as path from "path";
15
-
16
- // API endpoint - always available without auth
17
- const DICTIONARY_URL = "https://postgres.ai/api/general/checkup_dictionary";
18
-
19
- // Output path relative to cli/ directory
20
- const CLI_DIR = path.resolve(__dirname, "..");
21
- const OUTPUT_PATH = path.resolve(CLI_DIR, "lib/checkup-dictionary-embedded.ts");
22
-
23
- // Request timeout (10 seconds)
24
- const FETCH_TIMEOUT_MS = 10_000;
25
-
26
- interface CheckupDictionaryEntry {
27
- code: string;
28
- title: string;
29
- description: string;
30
- category: string;
31
- sort_order: number | null;
32
- is_system_report: boolean;
33
- }
34
-
35
- function generateTypeScript(data: CheckupDictionaryEntry[], sourceUrl: string): string {
36
- const lines: string[] = [
37
- "// AUTO-GENERATED FILE - DO NOT EDIT",
38
- `// Generated from: ${sourceUrl}`,
39
- `// Generated at: ${new Date().toISOString()}`,
40
- "// To regenerate: bun run embed-checkup-dictionary",
41
- "",
42
- 'import { CheckupDictionaryEntry } from "./checkup-dictionary";',
43
- "",
44
- "/**",
45
- " * Embedded checkup dictionary data fetched from API at build time.",
46
- " * Contains all available checkup report codes, titles, and metadata.",
47
- " */",
48
- `export const CHECKUP_DICTIONARY_DATA: CheckupDictionaryEntry[] = ${JSON.stringify(data, null, 2)};`,
49
- "",
50
- ];
51
- return lines.join("\n");
52
- }
53
-
54
- // Allowed hosts for fetch requests to prevent SSRF
55
- const ALLOWED_HOSTS = ["postgres.ai"];
56
-
57
- async function fetchWithTimeout(url: string, timeoutMs: number): Promise<Response> {
58
- // Validate URL against allowlist to prevent SSRF
59
- const parsed = new URL(url);
60
- if (!ALLOWED_HOSTS.includes(parsed.hostname)) {
61
- throw new Error(`Fetch blocked: host "${parsed.hostname}" is not in the allowlist`);
62
- }
63
-
64
- const controller = new AbortController();
65
- const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
66
-
67
- try {
68
- const response = await fetch(url, { signal: controller.signal });
69
- return response;
70
- } finally {
71
- clearTimeout(timeoutId);
72
- }
73
- }
74
-
75
- async function main() {
76
- console.log(`Fetching checkup dictionary from: ${DICTIONARY_URL}`);
77
-
78
- try {
79
- const response = await fetchWithTimeout(DICTIONARY_URL, FETCH_TIMEOUT_MS);
80
-
81
- if (!response.ok) {
82
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
83
- }
84
-
85
- const data: CheckupDictionaryEntry[] = await response.json();
86
-
87
- if (!Array.isArray(data)) {
88
- throw new Error("Expected array response from API");
89
- }
90
-
91
- // Validate entries have required fields
92
- for (const entry of data) {
93
- if (!entry.code || !entry.title) {
94
- throw new Error(`Invalid entry missing code or title: ${JSON.stringify(entry)}`);
95
- }
96
- }
97
-
98
- const tsCode = generateTypeScript(data, DICTIONARY_URL);
99
- fs.writeFileSync(OUTPUT_PATH, tsCode, "utf8");
100
-
101
- console.log(`Generated: ${OUTPUT_PATH}`);
102
- console.log(`Dictionary contains ${data.length} entries`);
103
- } catch (err) {
104
- const errorMsg = err instanceof Error ? err.message : String(err);
105
- console.warn(`Warning: Failed to fetch checkup dictionary: ${errorMsg}`);
106
- console.warn("Generating empty dictionary as fallback");
107
-
108
- // Generate empty dictionary to allow build to proceed
109
- const fallbackTs = generateTypeScript([], `N/A (fetch failed: ${errorMsg})`);
110
- fs.writeFileSync(OUTPUT_PATH, fallbackTs, "utf8");
111
- console.log(`Generated fallback dictionary at ${OUTPUT_PATH}`);
112
- }
113
- }
114
-
115
- main();
@@ -1,160 +0,0 @@
1
- #!/usr/bin/env bun
2
- /**
3
- * Build script to embed metrics.yml into the CLI bundle.
4
- *
5
- * This script reads config/pgwatch-prometheus/metrics.yml and generates
6
- * cli/lib/metrics-embedded.ts with the metrics data embedded as TypeScript.
7
- *
8
- * The generated file is NOT committed to git - it's regenerated at build time.
9
- *
10
- * Usage: bun run scripts/embed-metrics.ts
11
- */
12
-
13
- import * as fs from "fs";
14
- import * as path from "path";
15
- import * as yaml from "js-yaml";
16
-
17
- // Resolve paths relative to cli/ directory
18
- const CLI_DIR = path.resolve(__dirname, "..");
19
- const METRICS_YML_PATH = path.resolve(CLI_DIR, "../config/pgwatch-prometheus/metrics.yml");
20
- const OUTPUT_PATH = path.resolve(CLI_DIR, "lib/metrics-embedded.ts");
21
-
22
- interface MetricDefinition {
23
- description?: string;
24
- // YAML parses numeric keys (e.g., 11:, 14:) as numbers, representing PG major versions
25
- sqls: Record<number, string>;
26
- gauges?: string[];
27
- statement_timeout_seconds?: number;
28
- is_instance_level?: boolean;
29
- node_status?: string;
30
- }
31
-
32
- interface MetricsYml {
33
- metrics: Record<string, MetricDefinition>;
34
- }
35
-
36
- // Metrics needed for express mode reports
37
- const REQUIRED_METRICS = [
38
- // Settings and version (A002, A003, A007, A013)
39
- "settings",
40
- // Database stats (A004)
41
- "db_stats",
42
- "db_size",
43
- // Index health (H001, H002, H004)
44
- "pg_invalid_indexes",
45
- "unused_indexes",
46
- "redundant_indexes",
47
- // Stats reset info (H002)
48
- "stats_reset",
49
- // Dead tuples and per-table autovacuum overrides (F003)
50
- "pg_dead_tuples",
51
- // Bloat estimation (F004, F005)
52
- "pg_table_bloat",
53
- "pg_btree_bloat",
54
- // I/O statistics (I001)
55
- "pg_stat_io",
56
- ];
57
-
58
- function main() {
59
- console.log(`Reading metrics from: ${METRICS_YML_PATH}`);
60
-
61
- if (!fs.existsSync(METRICS_YML_PATH)) {
62
- console.error(`ERROR: metrics.yml not found at ${METRICS_YML_PATH}`);
63
- process.exit(1);
64
- }
65
-
66
- const yamlContent = fs.readFileSync(METRICS_YML_PATH, "utf8");
67
- const parsed = yaml.load(yamlContent) as MetricsYml;
68
-
69
- if (!parsed.metrics) {
70
- console.error("ERROR: No 'metrics' section found in metrics.yml");
71
- process.exit(1);
72
- }
73
-
74
- // Extract only required metrics
75
- const extractedMetrics: Record<string, MetricDefinition> = {};
76
- const missingMetrics: string[] = [];
77
-
78
- for (const metricName of REQUIRED_METRICS) {
79
- if (parsed.metrics[metricName]) {
80
- extractedMetrics[metricName] = parsed.metrics[metricName];
81
- } else {
82
- missingMetrics.push(metricName);
83
- }
84
- }
85
-
86
- if (missingMetrics.length > 0) {
87
- console.error(`ERROR: Missing required metrics: ${missingMetrics.join(", ")}`);
88
- process.exit(1);
89
- }
90
-
91
- // Generate TypeScript code
92
- const tsCode = generateTypeScript(extractedMetrics);
93
-
94
- // Write output
95
- fs.writeFileSync(OUTPUT_PATH, tsCode, "utf8");
96
- console.log(`Generated: ${OUTPUT_PATH}`);
97
- console.log(`Embedded ${Object.keys(extractedMetrics).length} metrics`);
98
- }
99
-
100
- function generateTypeScript(metrics: Record<string, MetricDefinition>): string {
101
- const lines: string[] = [
102
- "// AUTO-GENERATED FILE - DO NOT EDIT",
103
- "// Generated from config/pgwatch-prometheus/metrics.yml by scripts/embed-metrics.ts",
104
- `// Generated at: ${new Date().toISOString()}`,
105
- "",
106
- "/**",
107
- " * Metric definition from metrics.yml",
108
- " */",
109
- "export interface MetricDefinition {",
110
- " description?: string;",
111
- " sqls: Record<number, string>; // PG major version -> SQL query",
112
- " gauges?: string[];",
113
- " statement_timeout_seconds?: number;",
114
- "}",
115
- "",
116
- "/**",
117
- " * Embedded metrics for express mode reports.",
118
- " * Only includes metrics required for CLI checkup reports.",
119
- " */",
120
- "export const METRICS: Record<string, MetricDefinition> = {",
121
- ];
122
-
123
- for (const [name, metric] of Object.entries(metrics)) {
124
- lines.push(` ${JSON.stringify(name)}: {`);
125
-
126
- if (metric.description) {
127
- // Escape description for TypeScript string
128
- const desc = metric.description.trim().replace(/\n/g, " ").replace(/\s+/g, " ");
129
- lines.push(` description: ${JSON.stringify(desc)},`);
130
- }
131
-
132
- // sqls keys are PG major versions (numbers in YAML, but Object.entries returns strings)
133
- lines.push(" sqls: {");
134
- for (const [versionKey, sql] of Object.entries(metric.sqls)) {
135
- // YAML numeric keys may be parsed as numbers or strings depending on context;
136
- // explicitly convert to ensure consistent numeric keys in output
137
- const versionNum = typeof versionKey === "number" ? versionKey : parseInt(versionKey, 10);
138
- // Use JSON.stringify for robust escaping of all special characters
139
- lines.push(` ${versionNum}: ${JSON.stringify(sql)},`);
140
- }
141
- lines.push(" },");
142
-
143
- if (metric.gauges) {
144
- lines.push(` gauges: ${JSON.stringify(metric.gauges)},`);
145
- }
146
-
147
- if (metric.statement_timeout_seconds !== undefined) {
148
- lines.push(` statement_timeout_seconds: ${metric.statement_timeout_seconds},`);
149
- }
150
-
151
- lines.push(" },");
152
- }
153
-
154
- lines.push("};");
155
- lines.push("");
156
-
157
- return lines.join("\n");
158
- }
159
-
160
- main();