postgresai 0.14.0 → 0.15.0-dev.1

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.
@@ -24,6 +24,18 @@ import { getCheckupEntry } from "../lib/checkup-dictionary";
24
24
  import { createCheckupReport, uploadCheckupReportJson, convertCheckupReportJsonToMarkdown, RpcError, formatRpcErrorForDisplay, withRetry } from "../lib/checkup-api";
25
25
  import { generateCheckSummary } from "../lib/checkup-summary";
26
26
 
27
+ // Node.js version check - require Node 18+
28
+ // Node 14 reached EOL in April 2023, Node 16 in September 2023.
29
+ // Node 18+ is required for native ESM, modern crypto APIs, and security updates.
30
+ const nodeVersion = parseInt(process.versions.node.split('.')[0], 10);
31
+ if (nodeVersion < 18) {
32
+ console.error(`\x1b[31mError: postgresai requires Node 18 or higher.\x1b[0m`);
33
+ console.error(`You are running Node.js ${process.versions.node}.`);
34
+ console.error(`Please upgrade to Node.js 20 LTS or Node.js 22 for security updates.`);
35
+ console.error(`\nDownload: https://nodejs.org/`);
36
+ process.exit(1);
37
+ }
38
+
27
39
  // Singleton readline interface for stdin prompts
28
40
  let rl: ReturnType<typeof createInterface> | null = null;
29
41
  function getReadline() {
@@ -2061,6 +2073,85 @@ function checkRunningContainers(): { running: boolean; containers: string[] } {
2061
2073
  }
2062
2074
  }
2063
2075
 
2076
+ /**
2077
+ * Register monitoring instance with the API (non-blocking).
2078
+ * Returns immediately, logs result in background.
2079
+ */
2080
+ function registerMonitoringInstance(
2081
+ apiKey: string,
2082
+ projectName: string,
2083
+ opts?: { apiBaseUrl?: string; debug?: boolean }
2084
+ ): void {
2085
+ const { apiBaseUrl } = resolveBaseUrls(opts);
2086
+ const url = `${apiBaseUrl}/rpc/monitoring_instance_register`;
2087
+ const debug = opts?.debug;
2088
+
2089
+ if (debug) {
2090
+ console.log(`\nDebug: Registering monitoring instance...`);
2091
+ console.log(`Debug: POST ${url}`);
2092
+ console.log(`Debug: project_name=${projectName}`);
2093
+ }
2094
+
2095
+ // Fire and forget - don't block the main flow
2096
+ fetch(url, {
2097
+ method: "POST",
2098
+ headers: {
2099
+ "Content-Type": "application/json",
2100
+ },
2101
+ body: JSON.stringify({
2102
+ api_token: apiKey,
2103
+ project_name: projectName,
2104
+ }),
2105
+ })
2106
+ .then(async (res) => {
2107
+ const body = await res.text().catch(() => "");
2108
+ if (!res.ok) {
2109
+ if (debug) {
2110
+ console.log(`Debug: Monitoring registration failed: HTTP ${res.status}`);
2111
+ console.log(`Debug: Response: ${body}`);
2112
+ }
2113
+ return;
2114
+ }
2115
+ if (debug) {
2116
+ console.log(`Debug: Monitoring registration response: ${body}`);
2117
+ }
2118
+ })
2119
+ .catch((err) => {
2120
+ if (debug) {
2121
+ console.log(`Debug: Monitoring registration error: ${err.message}`);
2122
+ }
2123
+ });
2124
+ }
2125
+
2126
+ /**
2127
+ * Update .pgwatch-config file with key=value pairs.
2128
+ * Preserves existing values not being updated.
2129
+ */
2130
+ function updatePgwatchConfig(configPath: string, updates: Record<string, string>): void {
2131
+ let lines: string[] = [];
2132
+
2133
+ // Read existing config if it exists
2134
+ if (fs.existsSync(configPath)) {
2135
+ const stats = fs.statSync(configPath);
2136
+ if (!stats.isDirectory()) {
2137
+ const content = fs.readFileSync(configPath, "utf8");
2138
+ lines = content.split(/\r?\n/).filter(l => l.trim() !== "");
2139
+ }
2140
+ }
2141
+
2142
+ // Update or add each key
2143
+ for (const [key, value] of Object.entries(updates)) {
2144
+ const existingIndex = lines.findIndex(l => l.startsWith(key + "="));
2145
+ if (existingIndex >= 0) {
2146
+ lines[existingIndex] = `${key}=${value}`;
2147
+ } else {
2148
+ lines.push(`${key}=${value}`);
2149
+ }
2150
+ }
2151
+
2152
+ fs.writeFileSync(configPath, lines.join("\n") + "\n", { encoding: "utf8", mode: 0o600 });
2153
+ }
2154
+
2064
2155
  /**
2065
2156
  * Run docker compose command
2066
2157
  */
@@ -2145,12 +2236,13 @@ mon
2145
2236
  .option("--api-key <key>", "Postgres AI API key for automated report uploads")
2146
2237
  .option("--db-url <url>", "PostgreSQL connection URL to monitor")
2147
2238
  .option("--tag <tag>", "Docker image tag to use (e.g., 0.14.0, 0.14.0-dev.33)")
2239
+ .option("--project <name>", "Docker Compose project name (default: postgres_ai)")
2148
2240
  .option("-y, --yes", "accept all defaults and skip interactive prompts", false)
2149
- .action(async (opts: { demo: boolean; apiKey?: string; dbUrl?: string; tag?: string; yes: boolean }) => {
2241
+ .action(async (opts: { demo: boolean; apiKey?: string; dbUrl?: string; tag?: string; project?: string; yes: boolean }) => {
2150
2242
  // Get apiKey from global program options (--api-key is defined globally)
2151
2243
  // This is needed because Commander.js routes --api-key to the global option, not the subcommand's option
2152
2244
  const globalOpts = program.opts<CliOptions>();
2153
- const apiKey = opts.apiKey || globalOpts.apiKey;
2245
+ let apiKey = opts.apiKey || globalOpts.apiKey;
2154
2246
 
2155
2247
  console.log("\n=================================");
2156
2248
  console.log(" PostgresAI monitoring local install");
@@ -2161,6 +2253,13 @@ mon
2161
2253
  const { projectDir } = await resolveOrInitPaths();
2162
2254
  console.log(`Project directory: ${projectDir}\n`);
2163
2255
 
2256
+ // Save project name to .pgwatch-config if provided (used by reporter container)
2257
+ if (opts.project) {
2258
+ const cfgPath = path.resolve(projectDir, ".pgwatch-config");
2259
+ updatePgwatchConfig(cfgPath, { project_name: opts.project });
2260
+ console.log(`Using project name: ${opts.project}\n`);
2261
+ }
2262
+
2164
2263
  // Update .env with custom tag if provided
2165
2264
  const envFile = path.resolve(projectDir, ".env");
2166
2265
 
@@ -2230,10 +2329,7 @@ mon
2230
2329
  console.log("Using API key provided via --api-key parameter");
2231
2330
  config.writeConfig({ apiKey });
2232
2331
  // Keep reporter compatibility (docker-compose mounts .pgwatch-config)
2233
- fs.writeFileSync(path.resolve(projectDir, ".pgwatch-config"), `api_key=${apiKey}\n`, {
2234
- encoding: "utf8",
2235
- mode: 0o600
2236
- });
2332
+ updatePgwatchConfig(path.resolve(projectDir, ".pgwatch-config"), { api_key: apiKey });
2237
2333
  console.log("✓ API key saved\n");
2238
2334
  } else if (opts.yes) {
2239
2335
  // Auto-yes mode without API key - skip API key setup
@@ -2252,10 +2348,8 @@ mon
2252
2348
  if (trimmedKey) {
2253
2349
  config.writeConfig({ apiKey: trimmedKey });
2254
2350
  // Keep reporter compatibility (docker-compose mounts .pgwatch-config)
2255
- fs.writeFileSync(path.resolve(projectDir, ".pgwatch-config"), `api_key=${trimmedKey}\n`, {
2256
- encoding: "utf8",
2257
- mode: 0o600
2258
- });
2351
+ updatePgwatchConfig(path.resolve(projectDir, ".pgwatch-config"), { api_key: trimmedKey });
2352
+ apiKey = trimmedKey; // Update for later use in registerMonitoringInstance
2259
2353
  console.log("✓ API key saved\n");
2260
2354
  break;
2261
2355
  }
@@ -2442,6 +2536,15 @@ mon
2442
2536
  }
2443
2537
  console.log("✓ Services started\n");
2444
2538
 
2539
+ // Register monitoring instance with API (non-blocking, only if API key is configured)
2540
+ if (apiKey && !opts.demo) {
2541
+ const projectName = opts.project || "postgres-ai-monitoring";
2542
+ registerMonitoringInstance(apiKey, projectName, {
2543
+ apiBaseUrl: globalOpts.apiBaseUrl,
2544
+ debug: !!process.env.DEBUG,
2545
+ });
2546
+ }
2547
+
2445
2548
  // Final summary
2446
2549
  console.log("=================================");
2447
2550
  console.log(" Local install completed!");