postgresai 0.14.0-beta.1 → 0.14.0-beta.3

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 CHANGED
@@ -15,6 +15,8 @@ Or install the latest beta release explicitly:
15
15
  npm install -g postgresai@beta
16
16
  ```
17
17
 
18
+ Note: in this repository, `cli/package.json` uses a placeholder version (`0.0.0-dev.0`). The real published version is set by the git tag in CI when publishing to npm.
19
+
18
20
  ### From Homebrew (macOS)
19
21
 
20
22
  ```bash
@@ -27,11 +29,24 @@ brew install postgresai
27
29
 
28
30
  ## Usage
29
31
 
30
- The CLI provides three command aliases:
32
+ The `postgresai` package provides two command aliases (prefer `postgresai`):
31
33
  ```bash
32
34
  postgres-ai --help
33
35
  postgresai --help
34
- pgai --help # short alias
36
+ ```
37
+
38
+ You can also run it without installing via `npx`:
39
+
40
+ ```bash
41
+ npx postgresai --help
42
+ ```
43
+
44
+ ### Optional shorthand: `pgai`
45
+
46
+ If you want `npx pgai ...` as a shorthand for `npx postgresai ...`, install the separate `pgai` wrapper package:
47
+
48
+ ```bash
49
+ npx pgai --help
35
50
  ```
36
51
 
37
52
  ## init (create monitoring user in Postgres)
@@ -98,7 +113,7 @@ npx postgresai init postgresql://admin@host:5432/dbname --reset-password --passw
98
113
 
99
114
  Authenticate via browser to obtain API key:
100
115
  ```bash
101
- pgai auth
116
+ postgresai auth
102
117
  ```
103
118
 
104
119
  This will:
@@ -180,7 +195,7 @@ postgres-ai mon shell <service> # Open shell to monitoring servic
180
195
  ### MCP server (`mcp` group)
181
196
 
182
197
  ```bash
183
- pgai mcp start # Start MCP stdio server exposing tools
198
+ postgresai mcp start # Start MCP stdio server exposing tools
184
199
  ```
185
200
 
186
201
  Cursor configuration example (Settings → MCP):
@@ -189,7 +204,7 @@ Cursor configuration example (Settings → MCP):
189
204
  {
190
205
  "mcpServers": {
191
206
  "PostgresAI": {
192
- "command": "pgai",
207
+ "command": "postgresai",
193
208
  "args": ["mcp", "start"],
194
209
  "env": {
195
210
  "PGAI_API_BASE_URL": "https://postgres.ai/api/general/"
@@ -200,16 +215,16 @@ Cursor configuration example (Settings → MCP):
200
215
  ```
201
216
 
202
217
  Tools exposed:
203
- - list_issues: returns the same JSON as `pgai issues list`.
218
+ - list_issues: returns the same JSON as `postgresai issues list`.
204
219
  - view_issue: view a single issue with its comments (args: { issue_id, debug? })
205
220
  - post_issue_comment: post a comment (args: { issue_id, content, parent_comment_id?, debug? })
206
221
 
207
222
  ### Issues management (`issues` group)
208
223
 
209
224
  ```bash
210
- pgai issues list # List issues (shows: id, title, status, created_at)
211
- pgai issues view <issueId> # View issue details and comments
212
- pgai issues post_comment <issueId> <content> # Post a comment to an issue
225
+ postgresai issues list # List issues (shows: id, title, status, created_at)
226
+ postgresai issues view <issueId> # View issue details and comments
227
+ postgresai issues post_comment <issueId> <content> # Post a comment to an issue
213
228
  # Options:
214
229
  # --parent <uuid> Parent comment ID (for replies)
215
230
  # --debug Enable debug output
@@ -223,13 +238,13 @@ By default, issues commands print human-friendly YAML when writing to a terminal
223
238
  - Use `--json` to force JSON output:
224
239
 
225
240
  ```bash
226
- pgai issues list --json | jq '.[] | {id, title}'
241
+ postgresai issues list --json | jq '.[] | {id, title}'
227
242
  ```
228
243
 
229
244
  - Rely on auto-detection: when stdout is not a TTY (e.g., piped or redirected), output is JSON automatically:
230
245
 
231
246
  ```bash
232
- pgai issues view <issueId> > issue.json
247
+ postgresai issues view <issueId> > issue.json
233
248
  ```
234
249
 
235
250
  #### Grafana management
@@ -293,7 +308,7 @@ Linux/macOS (bash/zsh):
293
308
  ```bash
294
309
  export PGAI_API_BASE_URL=https://v2.postgres.ai/api/general/
295
310
  export PGAI_UI_BASE_URL=https://console-dev.postgres.ai
296
- pgai auth --debug
311
+ postgresai auth --debug
297
312
  ```
298
313
 
299
314
  Windows PowerShell:
@@ -301,13 +316,13 @@ Windows PowerShell:
301
316
  ```powershell
302
317
  $env:PGAI_API_BASE_URL = "https://v2.postgres.ai/api/general/"
303
318
  $env:PGAI_UI_BASE_URL = "https://console-dev.postgres.ai"
304
- pgai auth --debug
319
+ postgresai auth --debug
305
320
  ```
306
321
 
307
322
  Via CLI options (overrides env):
308
323
 
309
324
  ```bash
310
- pgai auth --debug \
325
+ postgresai auth --debug \
311
326
  --api-base-url https://v2.postgres.ai/api/general/ \
312
327
  --ui-base-url https://console-dev.postgres.ai
313
328
  ```
@@ -16,7 +16,7 @@ import { Client } from "pg";
16
16
  import { startMcpServer } from "../lib/mcp-server";
17
17
  import { fetchIssues, fetchIssueComments, createIssueComment, fetchIssue } from "../lib/issues";
18
18
  import { resolveBaseUrls } from "../lib/util";
19
- import { applyInitPlan, buildInitPlan, resolveAdminConnection, resolveMonitoringPassword, verifyInitSetup } from "../lib/init";
19
+ import { applyInitPlan, buildInitPlan, DEFAULT_MONITORING_USER, redactPasswordsInSql, resolveAdminConnection, resolveMonitoringPassword, verifyInitSetup } from "../lib/init";
20
20
 
21
21
  const execPromise = promisify(exec);
22
22
  const execFilePromise = promisify(execFile);
@@ -127,13 +127,12 @@ program
127
127
  .option("-U, --username <username>", "PostgreSQL user (psql-like)")
128
128
  .option("-d, --dbname <dbname>", "PostgreSQL database name (psql-like)")
129
129
  .option("--admin-password <password>", "Admin connection password (otherwise uses PGPASSWORD if set)")
130
- .option("--monitoring-user <name>", "Monitoring role name to create/update", "postgres_ai_mon")
130
+ .option("--monitoring-user <name>", "Monitoring role name to create/update", DEFAULT_MONITORING_USER)
131
131
  .option("--password <password>", "Monitoring role password (overrides PGAI_MON_PASSWORD)")
132
132
  .option("--skip-optional-permissions", "Skip optional permissions (RDS/self-managed extras)", false)
133
133
  .option("--verify", "Verify that monitoring role/permissions are in place (no changes)", false)
134
134
  .option("--reset-password", "Reset monitoring role password only (no other changes)", false)
135
135
  .option("--print-sql", "Print SQL plan and exit (no changes applied)", false)
136
- .option("--show-secrets", "When printing SQL, do not redact secrets (DANGEROUS)", false)
137
136
  .option("--print-password", "Print generated monitoring password (DANGEROUS in CI logs)", false)
138
137
  .addHelpText(
139
138
  "after",
@@ -152,6 +151,11 @@ program
152
151
  " If auto-generated, it is printed only on TTY by default.",
153
152
  " To print it in non-interactive mode: --print-password",
154
153
  "",
154
+ "Environment variables (libpq standard):",
155
+ " PGHOST, PGPORT, PGUSER, PGDATABASE — connection defaults",
156
+ " PGPASSWORD — admin password",
157
+ " PGAI_MON_PASSWORD — monitoring password",
158
+ "",
155
159
  "Inspect SQL without applying changes:",
156
160
  " postgresai init <conn> --print-sql",
157
161
  "",
@@ -162,7 +166,7 @@ program
162
166
  " postgresai init <conn> --reset-password --password '...'",
163
167
  "",
164
168
  "Offline SQL plan (no DB connection):",
165
- " postgresai init --print-sql -d dbname --password '...' --show-secrets",
169
+ " postgresai init --print-sql",
166
170
  ].join("\n")
167
171
  )
168
172
  .action(async (conn: string | undefined, opts: {
@@ -178,7 +182,6 @@ program
178
182
  verify?: boolean;
179
183
  resetPassword?: boolean;
180
184
  printSql?: boolean;
181
- showSecrets?: boolean;
182
185
  printPassword?: boolean;
183
186
  }, cmd: Command) => {
184
187
  if (opts.verify && opts.resetPassword) {
@@ -193,30 +196,25 @@ program
193
196
  }
194
197
 
195
198
  const shouldPrintSql = !!opts.printSql;
196
- const shouldRedactSecrets = !opts.showSecrets;
197
- const redactPasswords = (sql: string): string => {
198
- if (!shouldRedactSecrets) return sql;
199
- // Replace PASSWORD '<literal>' (handles doubled quotes inside).
200
- return sql.replace(/password\s+'(?:''|[^'])*'/gi, "password '<redacted>'");
201
- };
199
+ const redactPasswords = (sql: string): string => redactPasswordsInSql(sql);
202
200
 
203
201
  // Offline mode: allow printing SQL without providing/using an admin connection.
204
- // Useful for audits/reviews; caller can provide -d/PGDATABASE and an explicit monitoring password.
202
+ // Useful for audits/reviews; caller can provide -d/PGDATABASE.
205
203
  if (!conn && !opts.dbUrl && !opts.host && !opts.port && !opts.username && !opts.adminPassword) {
206
204
  if (shouldPrintSql) {
207
205
  const database = (opts.dbname ?? process.env.PGDATABASE ?? "postgres").trim();
208
206
  const includeOptionalPermissions = !opts.skipOptionalPermissions;
209
207
 
210
- // Use explicit password/env if provided; otherwise use a placeholder (will be redacted unless --show-secrets).
208
+ // Use explicit password/env if provided; otherwise use a placeholder.
209
+ // Printed SQL always redacts secrets.
211
210
  const monPassword =
212
- (opts.password ?? process.env.PGAI_MON_PASSWORD ?? "CHANGE_ME").toString();
211
+ (opts.password ?? process.env.PGAI_MON_PASSWORD ?? "<redacted>").toString();
213
212
 
214
213
  const plan = await buildInitPlan({
215
214
  database,
216
215
  monitoringUser: opts.monitoringUser,
217
216
  monitoringPassword: monPassword,
218
217
  includeOptionalPermissions,
219
- roleExists: undefined,
220
218
  });
221
219
 
222
220
  console.log("\n--- SQL plan (offline; not connected) ---");
@@ -228,9 +226,7 @@ program
228
226
  console.log(redactPasswords(step.sql));
229
227
  }
230
228
  console.log("\n--- end SQL plan ---\n");
231
- if (shouldRedactSecrets) {
232
- console.log("Note: passwords are redacted in the printed SQL (use --show-secrets to print them).");
233
- }
229
+ console.log("Note: passwords are redacted in the printed SQL output.");
234
230
  return;
235
231
  }
236
232
  }
@@ -250,7 +246,7 @@ program
250
246
  });
251
247
  } catch (e) {
252
248
  const msg = e instanceof Error ? e.message : String(e);
253
- console.error(`✗ ${msg}`);
249
+ console.error(`Error: init: ${msg}`);
254
250
  // When connection details are missing, show full init help (options + examples).
255
251
  if (typeof msg === "string" && msg.startsWith("Connection is required.")) {
256
252
  console.error("");
@@ -267,16 +263,11 @@ program
267
263
  console.log(`Optional permissions: ${includeOptionalPermissions ? "enabled" : "skipped"}`);
268
264
 
269
265
  // Use native pg client instead of requiring psql to be installed
270
- const client = new Client(adminConn.clientConfig);
271
-
266
+ let client: Client | undefined;
272
267
  try {
268
+ client = new Client(adminConn.clientConfig);
273
269
  await client.connect();
274
270
 
275
- const roleRes = await client.query("select 1 from pg_catalog.pg_roles where rolname = $1", [
276
- opts.monitoringUser,
277
- ]);
278
- const roleExists = (roleRes.rowCount ?? 0) > 0;
279
-
280
271
  const dbRes = await client.query("select current_database() as db");
281
272
  const database = dbRes.rows?.[0]?.db;
282
273
  if (typeof database !== "string" || !database) {
@@ -319,10 +310,13 @@ program
319
310
  if (resolved.generated) {
320
311
  const canPrint = process.stdout.isTTY || !!opts.printPassword;
321
312
  if (canPrint) {
322
- console.log("");
323
- console.log(`Generated monitoring password for ${opts.monitoringUser} (copy/paste):`);
324
- console.log(`PGAI_MON_PASSWORD=${monPassword}`);
325
- console.log("");
313
+ // Print secrets to stderr to reduce the chance they end up in piped stdout logs.
314
+ const shellSafe = monPassword.replace(/'/g, "'\\''");
315
+ console.error("");
316
+ console.error(`Generated monitoring password for ${opts.monitoringUser} (copy/paste):`);
317
+ // Quote for shell copy/paste safety.
318
+ console.error(`PGAI_MON_PASSWORD='${shellSafe}'`);
319
+ console.error("");
326
320
  console.log("Store it securely (or rerun with --password / PGAI_MON_PASSWORD to set your own).");
327
321
  } else {
328
322
  console.error(
@@ -352,7 +346,6 @@ program
352
346
  monitoringUser: opts.monitoringUser,
353
347
  monitoringPassword: monPassword,
354
348
  includeOptionalPermissions,
355
- roleExists,
356
349
  });
357
350
 
358
351
  const effectivePlan = opts.resetPassword
@@ -366,9 +359,7 @@ program
366
359
  console.log(redactPasswords(step.sql));
367
360
  }
368
361
  console.log("\n--- end SQL plan ---\n");
369
- if (shouldRedactSecrets) {
370
- console.log("Note: passwords are redacted in the printed SQL (use --show-secrets to print them).");
371
- }
362
+ console.log("Note: passwords are redacted in the printed SQL output.");
372
363
  return;
373
364
  }
374
365
 
@@ -396,57 +387,54 @@ program
396
387
  if (!message || message === "[object Object]") {
397
388
  message = "Unknown error";
398
389
  }
399
- console.error(`✗ init failed: ${message}`);
390
+ console.error(`Error: init: ${message}`);
400
391
  // If this was a plan step failure, surface the step name explicitly to help users diagnose quickly.
401
392
  const stepMatch =
402
393
  typeof message === "string" ? message.match(/Failed at step "([^"]+)":/i) : null;
403
394
  const failedStep = stepMatch?.[1];
404
395
  if (failedStep) {
405
- console.error(`Step: ${failedStep}`);
396
+ console.error(` Step: ${failedStep}`);
406
397
  }
407
398
  if (errAny && typeof errAny === "object") {
408
399
  if (typeof errAny.code === "string" && errAny.code) {
409
- console.error(`Error code: ${errAny.code}`);
400
+ console.error(` Code: ${errAny.code}`);
410
401
  }
411
402
  if (typeof errAny.detail === "string" && errAny.detail) {
412
- console.error(`Detail: ${errAny.detail}`);
403
+ console.error(` Detail: ${errAny.detail}`);
413
404
  }
414
405
  if (typeof errAny.hint === "string" && errAny.hint) {
415
- console.error(`Hint: ${errAny.hint}`);
406
+ console.error(` Hint: ${errAny.hint}`);
416
407
  }
417
408
  }
418
409
  if (errAny && typeof errAny === "object" && typeof errAny.code === "string") {
419
410
  if (errAny.code === "42501") {
420
- console.error("");
421
- console.error("Permission error: your admin connection is not allowed to complete the setup.");
422
411
  if (failedStep === "01.role") {
423
- console.error("What failed: create/update the monitoring role (needs CREATEROLE or superuser).");
412
+ console.error(" Context: role creation/update requires CREATEROLE or superuser");
424
413
  } else if (failedStep === "02.permissions") {
425
- console.error("What failed: grant required permissions / create view / set role search_path.");
414
+ console.error(" Context: grants/view/search_path require sufficient GRANT/DDL privileges");
426
415
  }
427
- console.error("How to fix:");
428
- console.error("- Connect as a superuser (or a role with CREATEROLE and sufficient GRANT privileges).");
429
- console.error("- On managed Postgres, use the provider's admin/master user.");
430
- console.error("Tip: run with --print-sql to review the exact SQL plan.");
431
- console.error("");
432
- console.error("Hint: connect as a superuser (or a role with CREATEROLE and sufficient GRANT privileges).");
416
+ console.error(" Fix: connect as a superuser (or a role with CREATEROLE and sufficient GRANT privileges)");
417
+ console.error(" Fix: on managed Postgres, use the provider's admin/master user");
418
+ console.error(" Tip: run with --print-sql to review the exact SQL plan");
433
419
  }
434
420
  if (errAny.code === "ECONNREFUSED") {
435
- console.error("Hint: check host/port and ensure Postgres is reachable from this machine.");
421
+ console.error(" Hint: check host/port and ensure Postgres is reachable from this machine");
436
422
  }
437
423
  if (errAny.code === "ENOTFOUND") {
438
- console.error("Hint: DNS resolution failed; double-check the host name.");
424
+ console.error(" Hint: DNS resolution failed; double-check the host name");
439
425
  }
440
426
  if (errAny.code === "ETIMEDOUT") {
441
- console.error("Hint: connection timed out; check network/firewall rules.");
427
+ console.error(" Hint: connection timed out; check network/firewall rules");
442
428
  }
443
429
  }
444
430
  process.exitCode = 1;
445
431
  } finally {
446
- try {
447
- await client.end();
448
- } catch {
449
- // ignore
432
+ if (client) {
433
+ try {
434
+ await client.end();
435
+ } catch {
436
+ // ignore
437
+ }
450
438
  }
451
439
  }
452
440
  });
@@ -109,13 +109,12 @@ program
109
109
  .option("-U, --username <username>", "PostgreSQL user (psql-like)")
110
110
  .option("-d, --dbname <dbname>", "PostgreSQL database name (psql-like)")
111
111
  .option("--admin-password <password>", "Admin connection password (otherwise uses PGPASSWORD if set)")
112
- .option("--monitoring-user <name>", "Monitoring role name to create/update", "postgres_ai_mon")
112
+ .option("--monitoring-user <name>", "Monitoring role name to create/update", init_1.DEFAULT_MONITORING_USER)
113
113
  .option("--password <password>", "Monitoring role password (overrides PGAI_MON_PASSWORD)")
114
114
  .option("--skip-optional-permissions", "Skip optional permissions (RDS/self-managed extras)", false)
115
115
  .option("--verify", "Verify that monitoring role/permissions are in place (no changes)", false)
116
116
  .option("--reset-password", "Reset monitoring role password only (no other changes)", false)
117
117
  .option("--print-sql", "Print SQL plan and exit (no changes applied)", false)
118
- .option("--show-secrets", "When printing SQL, do not redact secrets (DANGEROUS)", false)
119
118
  .option("--print-password", "Print generated monitoring password (DANGEROUS in CI logs)", false)
120
119
  .addHelpText("after", [
121
120
  "",
@@ -132,6 +131,11 @@ program
132
131
  " If auto-generated, it is printed only on TTY by default.",
133
132
  " To print it in non-interactive mode: --print-password",
134
133
  "",
134
+ "Environment variables (libpq standard):",
135
+ " PGHOST, PGPORT, PGUSER, PGDATABASE — connection defaults",
136
+ " PGPASSWORD — admin password",
137
+ " PGAI_MON_PASSWORD — monitoring password",
138
+ "",
135
139
  "Inspect SQL without applying changes:",
136
140
  " postgresai init <conn> --print-sql",
137
141
  "",
@@ -142,7 +146,7 @@ program
142
146
  " postgresai init <conn> --reset-password --password '...'",
143
147
  "",
144
148
  "Offline SQL plan (no DB connection):",
145
- " postgresai init --print-sql -d dbname --password '...' --show-secrets",
149
+ " postgresai init --print-sql",
146
150
  ].join("\n"))
147
151
  .action(async (conn, opts, cmd) => {
148
152
  if (opts.verify && opts.resetPassword) {
@@ -156,27 +160,21 @@ program
156
160
  return;
157
161
  }
158
162
  const shouldPrintSql = !!opts.printSql;
159
- const shouldRedactSecrets = !opts.showSecrets;
160
- const redactPasswords = (sql) => {
161
- if (!shouldRedactSecrets)
162
- return sql;
163
- // Replace PASSWORD '<literal>' (handles doubled quotes inside).
164
- return sql.replace(/password\s+'(?:''|[^'])*'/gi, "password '<redacted>'");
165
- };
163
+ const redactPasswords = (sql) => (0, init_1.redactPasswordsInSql)(sql);
166
164
  // Offline mode: allow printing SQL without providing/using an admin connection.
167
- // Useful for audits/reviews; caller can provide -d/PGDATABASE and an explicit monitoring password.
165
+ // Useful for audits/reviews; caller can provide -d/PGDATABASE.
168
166
  if (!conn && !opts.dbUrl && !opts.host && !opts.port && !opts.username && !opts.adminPassword) {
169
167
  if (shouldPrintSql) {
170
168
  const database = (opts.dbname ?? process.env.PGDATABASE ?? "postgres").trim();
171
169
  const includeOptionalPermissions = !opts.skipOptionalPermissions;
172
- // Use explicit password/env if provided; otherwise use a placeholder (will be redacted unless --show-secrets).
173
- const monPassword = (opts.password ?? process.env.PGAI_MON_PASSWORD ?? "CHANGE_ME").toString();
170
+ // Use explicit password/env if provided; otherwise use a placeholder.
171
+ // Printed SQL always redacts secrets.
172
+ const monPassword = (opts.password ?? process.env.PGAI_MON_PASSWORD ?? "<redacted>").toString();
174
173
  const plan = await (0, init_1.buildInitPlan)({
175
174
  database,
176
175
  monitoringUser: opts.monitoringUser,
177
176
  monitoringPassword: monPassword,
178
177
  includeOptionalPermissions,
179
- roleExists: undefined,
180
178
  });
181
179
  console.log("\n--- SQL plan (offline; not connected) ---");
182
180
  console.log(`-- database: ${database}`);
@@ -187,9 +185,7 @@ program
187
185
  console.log(redactPasswords(step.sql));
188
186
  }
189
187
  console.log("\n--- end SQL plan ---\n");
190
- if (shouldRedactSecrets) {
191
- console.log("Note: passwords are redacted in the printed SQL (use --show-secrets to print them).");
192
- }
188
+ console.log("Note: passwords are redacted in the printed SQL output.");
193
189
  return;
194
190
  }
195
191
  }
@@ -209,7 +205,7 @@ program
209
205
  }
210
206
  catch (e) {
211
207
  const msg = e instanceof Error ? e.message : String(e);
212
- console.error(`✗ ${msg}`);
208
+ console.error(`Error: init: ${msg}`);
213
209
  // When connection details are missing, show full init help (options + examples).
214
210
  if (typeof msg === "string" && msg.startsWith("Connection is required.")) {
215
211
  console.error("");
@@ -223,13 +219,10 @@ program
223
219
  console.log(`Monitoring user: ${opts.monitoringUser}`);
224
220
  console.log(`Optional permissions: ${includeOptionalPermissions ? "enabled" : "skipped"}`);
225
221
  // Use native pg client instead of requiring psql to be installed
226
- const client = new pg_1.Client(adminConn.clientConfig);
222
+ let client;
227
223
  try {
224
+ client = new pg_1.Client(adminConn.clientConfig);
228
225
  await client.connect();
229
- const roleRes = await client.query("select 1 from pg_catalog.pg_roles where rolname = $1", [
230
- opts.monitoringUser,
231
- ]);
232
- const roleExists = (roleRes.rowCount ?? 0) > 0;
233
226
  const dbRes = await client.query("select current_database() as db");
234
227
  const database = dbRes.rows?.[0]?.db;
235
228
  if (typeof database !== "string" || !database) {
@@ -273,10 +266,13 @@ program
273
266
  if (resolved.generated) {
274
267
  const canPrint = process.stdout.isTTY || !!opts.printPassword;
275
268
  if (canPrint) {
276
- console.log("");
277
- console.log(`Generated monitoring password for ${opts.monitoringUser} (copy/paste):`);
278
- console.log(`PGAI_MON_PASSWORD=${monPassword}`);
279
- console.log("");
269
+ // Print secrets to stderr to reduce the chance they end up in piped stdout logs.
270
+ const shellSafe = monPassword.replace(/'/g, "'\\''");
271
+ console.error("");
272
+ console.error(`Generated monitoring password for ${opts.monitoringUser} (copy/paste):`);
273
+ // Quote for shell copy/paste safety.
274
+ console.error(`PGAI_MON_PASSWORD='${shellSafe}'`);
275
+ console.error("");
280
276
  console.log("Store it securely (or rerun with --password / PGAI_MON_PASSWORD to set your own).");
281
277
  }
282
278
  else {
@@ -305,7 +301,6 @@ program
305
301
  monitoringUser: opts.monitoringUser,
306
302
  monitoringPassword: monPassword,
307
303
  includeOptionalPermissions,
308
- roleExists,
309
304
  });
310
305
  const effectivePlan = opts.resetPassword
311
306
  ? { ...plan, steps: plan.steps.filter((s) => s.name === "01.role") }
@@ -317,9 +312,7 @@ program
317
312
  console.log(redactPasswords(step.sql));
318
313
  }
319
314
  console.log("\n--- end SQL plan ---\n");
320
- if (shouldRedactSecrets) {
321
- console.log("Note: passwords are redacted in the printed SQL (use --show-secrets to print them).");
322
- }
315
+ console.log("Note: passwords are redacted in the printed SQL output.");
323
316
  return;
324
317
  }
325
318
  const { applied, skippedOptional } = await (0, init_1.applyInitPlan)({ client, plan: effectivePlan });
@@ -349,59 +342,56 @@ program
349
342
  if (!message || message === "[object Object]") {
350
343
  message = "Unknown error";
351
344
  }
352
- console.error(`✗ init failed: ${message}`);
345
+ console.error(`Error: init: ${message}`);
353
346
  // If this was a plan step failure, surface the step name explicitly to help users diagnose quickly.
354
347
  const stepMatch = typeof message === "string" ? message.match(/Failed at step "([^"]+)":/i) : null;
355
348
  const failedStep = stepMatch?.[1];
356
349
  if (failedStep) {
357
- console.error(`Step: ${failedStep}`);
350
+ console.error(` Step: ${failedStep}`);
358
351
  }
359
352
  if (errAny && typeof errAny === "object") {
360
353
  if (typeof errAny.code === "string" && errAny.code) {
361
- console.error(`Error code: ${errAny.code}`);
354
+ console.error(` Code: ${errAny.code}`);
362
355
  }
363
356
  if (typeof errAny.detail === "string" && errAny.detail) {
364
- console.error(`Detail: ${errAny.detail}`);
357
+ console.error(` Detail: ${errAny.detail}`);
365
358
  }
366
359
  if (typeof errAny.hint === "string" && errAny.hint) {
367
- console.error(`Hint: ${errAny.hint}`);
360
+ console.error(` Hint: ${errAny.hint}`);
368
361
  }
369
362
  }
370
363
  if (errAny && typeof errAny === "object" && typeof errAny.code === "string") {
371
364
  if (errAny.code === "42501") {
372
- console.error("");
373
- console.error("Permission error: your admin connection is not allowed to complete the setup.");
374
365
  if (failedStep === "01.role") {
375
- console.error("What failed: create/update the monitoring role (needs CREATEROLE or superuser).");
366
+ console.error(" Context: role creation/update requires CREATEROLE or superuser");
376
367
  }
377
368
  else if (failedStep === "02.permissions") {
378
- console.error("What failed: grant required permissions / create view / set role search_path.");
369
+ console.error(" Context: grants/view/search_path require sufficient GRANT/DDL privileges");
379
370
  }
380
- console.error("How to fix:");
381
- console.error("- Connect as a superuser (or a role with CREATEROLE and sufficient GRANT privileges).");
382
- console.error("- On managed Postgres, use the provider's admin/master user.");
383
- console.error("Tip: run with --print-sql to review the exact SQL plan.");
384
- console.error("");
385
- console.error("Hint: connect as a superuser (or a role with CREATEROLE and sufficient GRANT privileges).");
371
+ console.error(" Fix: connect as a superuser (or a role with CREATEROLE and sufficient GRANT privileges)");
372
+ console.error(" Fix: on managed Postgres, use the provider's admin/master user");
373
+ console.error(" Tip: run with --print-sql to review the exact SQL plan");
386
374
  }
387
375
  if (errAny.code === "ECONNREFUSED") {
388
- console.error("Hint: check host/port and ensure Postgres is reachable from this machine.");
376
+ console.error(" Hint: check host/port and ensure Postgres is reachable from this machine");
389
377
  }
390
378
  if (errAny.code === "ENOTFOUND") {
391
- console.error("Hint: DNS resolution failed; double-check the host name.");
379
+ console.error(" Hint: DNS resolution failed; double-check the host name");
392
380
  }
393
381
  if (errAny.code === "ETIMEDOUT") {
394
- console.error("Hint: connection timed out; check network/firewall rules.");
382
+ console.error(" Hint: connection timed out; check network/firewall rules");
395
383
  }
396
384
  }
397
385
  process.exitCode = 1;
398
386
  }
399
387
  finally {
400
- try {
401
- await client.end();
402
- }
403
- catch {
404
- // ignore
388
+ if (client) {
389
+ try {
390
+ await client.end();
391
+ }
392
+ catch {
393
+ // ignore
394
+ }
405
395
  }
406
396
  }
407
397
  });