postgresai 0.14.0-dev.14 → 0.14.0-dev.16

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
@@ -31,7 +33,12 @@ The CLI provides three command aliases:
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
35
42
  ```
36
43
 
37
44
  ## init (create monitoring user in Postgres)
@@ -98,7 +105,7 @@ npx postgresai init postgresql://admin@host:5432/dbname --reset-password --passw
98
105
 
99
106
  Authenticate via browser to obtain API key:
100
107
  ```bash
101
- pgai auth
108
+ postgresai auth
102
109
  ```
103
110
 
104
111
  This will:
@@ -180,7 +187,7 @@ postgres-ai mon shell <service> # Open shell to monitoring servic
180
187
  ### MCP server (`mcp` group)
181
188
 
182
189
  ```bash
183
- pgai mcp start # Start MCP stdio server exposing tools
190
+ postgresai mcp start # Start MCP stdio server exposing tools
184
191
  ```
185
192
 
186
193
  Cursor configuration example (Settings → MCP):
@@ -189,7 +196,7 @@ Cursor configuration example (Settings → MCP):
189
196
  {
190
197
  "mcpServers": {
191
198
  "PostgresAI": {
192
- "command": "pgai",
199
+ "command": "postgresai",
193
200
  "args": ["mcp", "start"],
194
201
  "env": {
195
202
  "PGAI_API_BASE_URL": "https://postgres.ai/api/general/"
@@ -200,16 +207,16 @@ Cursor configuration example (Settings → MCP):
200
207
  ```
201
208
 
202
209
  Tools exposed:
203
- - list_issues: returns the same JSON as `pgai issues list`.
210
+ - list_issues: returns the same JSON as `postgresai issues list`.
204
211
  - view_issue: view a single issue with its comments (args: { issue_id, debug? })
205
212
  - post_issue_comment: post a comment (args: { issue_id, content, parent_comment_id?, debug? })
206
213
 
207
214
  ### Issues management (`issues` group)
208
215
 
209
216
  ```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
217
+ postgresai issues list # List issues (shows: id, title, status, created_at)
218
+ postgresai issues view <issueId> # View issue details and comments
219
+ postgresai issues post_comment <issueId> <content> # Post a comment to an issue
213
220
  # Options:
214
221
  # --parent <uuid> Parent comment ID (for replies)
215
222
  # --debug Enable debug output
@@ -223,13 +230,13 @@ By default, issues commands print human-friendly YAML when writing to a terminal
223
230
  - Use `--json` to force JSON output:
224
231
 
225
232
  ```bash
226
- pgai issues list --json | jq '.[] | {id, title}'
233
+ postgresai issues list --json | jq '.[] | {id, title}'
227
234
  ```
228
235
 
229
236
  - Rely on auto-detection: when stdout is not a TTY (e.g., piped or redirected), output is JSON automatically:
230
237
 
231
238
  ```bash
232
- pgai issues view <issueId> > issue.json
239
+ postgresai issues view <issueId> > issue.json
233
240
  ```
234
241
 
235
242
  #### Grafana management
@@ -293,7 +300,7 @@ Linux/macOS (bash/zsh):
293
300
  ```bash
294
301
  export PGAI_API_BASE_URL=https://v2.postgres.ai/api/general/
295
302
  export PGAI_UI_BASE_URL=https://console-dev.postgres.ai
296
- pgai auth --debug
303
+ postgresai auth --debug
297
304
  ```
298
305
 
299
306
  Windows PowerShell:
@@ -301,13 +308,13 @@ Windows PowerShell:
301
308
  ```powershell
302
309
  $env:PGAI_API_BASE_URL = "https://v2.postgres.ai/api/general/"
303
310
  $env:PGAI_UI_BASE_URL = "https://console-dev.postgres.ai"
304
- pgai auth --debug
311
+ postgresai auth --debug
305
312
  ```
306
313
 
307
314
  Via CLI options (overrides env):
308
315
 
309
316
  ```bash
310
- pgai auth --debug \
317
+ postgresai auth --debug \
311
318
  --api-base-url https://v2.postgres.ai/api/general/ \
312
319
  --ui-base-url https://console-dev.postgres.ai
313
320
  ```
@@ -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,7 +127,7 @@ 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)
@@ -152,6 +152,11 @@ program
152
152
  " If auto-generated, it is printed only on TTY by default.",
153
153
  " To print it in non-interactive mode: --print-password",
154
154
  "",
155
+ "Environment variables (libpq standard):",
156
+ " PGHOST, PGPORT, PGUSER, PGDATABASE — connection defaults",
157
+ " PGPASSWORD — admin password",
158
+ " PGAI_MON_PASSWORD — monitoring password",
159
+ "",
155
160
  "Inspect SQL without applying changes:",
156
161
  " postgresai init <conn> --print-sql",
157
162
  "",
@@ -197,7 +202,7 @@ program
197
202
  const redactPasswords = (sql: string): string => {
198
203
  if (!shouldRedactSecrets) return sql;
199
204
  // Replace PASSWORD '<literal>' (handles doubled quotes inside).
200
- return sql.replace(/password\s+'(?:''|[^'])*'/gi, "password '<redacted>'");
205
+ return redactPasswordsInSql(sql);
201
206
  };
202
207
 
203
208
  // Offline mode: allow printing SQL without providing/using an admin connection.
@@ -216,7 +221,6 @@ program
216
221
  monitoringUser: opts.monitoringUser,
217
222
  monitoringPassword: monPassword,
218
223
  includeOptionalPermissions,
219
- roleExists: undefined,
220
224
  });
221
225
 
222
226
  console.log("\n--- SQL plan (offline; not connected) ---");
@@ -250,7 +254,7 @@ program
250
254
  });
251
255
  } catch (e) {
252
256
  const msg = e instanceof Error ? e.message : String(e);
253
- console.error(`✗ ${msg}`);
257
+ console.error(`Error: init: ${msg}`);
254
258
  // When connection details are missing, show full init help (options + examples).
255
259
  if (typeof msg === "string" && msg.startsWith("Connection is required.")) {
256
260
  console.error("");
@@ -267,16 +271,11 @@ program
267
271
  console.log(`Optional permissions: ${includeOptionalPermissions ? "enabled" : "skipped"}`);
268
272
 
269
273
  // Use native pg client instead of requiring psql to be installed
270
- const client = new Client(adminConn.clientConfig);
271
-
274
+ let client: Client | undefined;
272
275
  try {
276
+ client = new Client(adminConn.clientConfig);
273
277
  await client.connect();
274
278
 
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
279
  const dbRes = await client.query("select current_database() as db");
281
280
  const database = dbRes.rows?.[0]?.db;
282
281
  if (typeof database !== "string" || !database) {
@@ -319,10 +318,13 @@ program
319
318
  if (resolved.generated) {
320
319
  const canPrint = process.stdout.isTTY || !!opts.printPassword;
321
320
  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("");
321
+ // Print secrets to stderr to reduce the chance they end up in piped stdout logs.
322
+ const shellSafe = monPassword.replace(/'/g, "'\\''");
323
+ console.error("");
324
+ console.error(`Generated monitoring password for ${opts.monitoringUser} (copy/paste):`);
325
+ // Quote for shell copy/paste safety.
326
+ console.error(`PGAI_MON_PASSWORD='${shellSafe}'`);
327
+ console.error("");
326
328
  console.log("Store it securely (or rerun with --password / PGAI_MON_PASSWORD to set your own).");
327
329
  } else {
328
330
  console.error(
@@ -352,7 +354,6 @@ program
352
354
  monitoringUser: opts.monitoringUser,
353
355
  monitoringPassword: monPassword,
354
356
  includeOptionalPermissions,
355
- roleExists,
356
357
  });
357
358
 
358
359
  const effectivePlan = opts.resetPassword
@@ -396,57 +397,54 @@ program
396
397
  if (!message || message === "[object Object]") {
397
398
  message = "Unknown error";
398
399
  }
399
- console.error(`✗ init failed: ${message}`);
400
+ console.error(`Error: init: ${message}`);
400
401
  // If this was a plan step failure, surface the step name explicitly to help users diagnose quickly.
401
402
  const stepMatch =
402
403
  typeof message === "string" ? message.match(/Failed at step "([^"]+)":/i) : null;
403
404
  const failedStep = stepMatch?.[1];
404
405
  if (failedStep) {
405
- console.error(`Step: ${failedStep}`);
406
+ console.error(` Step: ${failedStep}`);
406
407
  }
407
408
  if (errAny && typeof errAny === "object") {
408
409
  if (typeof errAny.code === "string" && errAny.code) {
409
- console.error(`Error code: ${errAny.code}`);
410
+ console.error(` Code: ${errAny.code}`);
410
411
  }
411
412
  if (typeof errAny.detail === "string" && errAny.detail) {
412
- console.error(`Detail: ${errAny.detail}`);
413
+ console.error(` Detail: ${errAny.detail}`);
413
414
  }
414
415
  if (typeof errAny.hint === "string" && errAny.hint) {
415
- console.error(`Hint: ${errAny.hint}`);
416
+ console.error(` Hint: ${errAny.hint}`);
416
417
  }
417
418
  }
418
419
  if (errAny && typeof errAny === "object" && typeof errAny.code === "string") {
419
420
  if (errAny.code === "42501") {
420
- console.error("");
421
- console.error("Permission error: your admin connection is not allowed to complete the setup.");
422
421
  if (failedStep === "01.role") {
423
- console.error("What failed: create/update the monitoring role (needs CREATEROLE or superuser).");
422
+ console.error(" Context: role creation/update requires CREATEROLE or superuser");
424
423
  } else if (failedStep === "02.permissions") {
425
- console.error("What failed: grant required permissions / create view / set role search_path.");
424
+ console.error(" Context: grants/view/search_path require sufficient GRANT/DDL privileges");
426
425
  }
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).");
426
+ console.error(" Fix: connect as a superuser (or a role with CREATEROLE and sufficient GRANT privileges)");
427
+ console.error(" Fix: on managed Postgres, use the provider's admin/master user");
428
+ console.error(" Tip: run with --print-sql to review the exact SQL plan");
433
429
  }
434
430
  if (errAny.code === "ECONNREFUSED") {
435
- console.error("Hint: check host/port and ensure Postgres is reachable from this machine.");
431
+ console.error(" Hint: check host/port and ensure Postgres is reachable from this machine");
436
432
  }
437
433
  if (errAny.code === "ENOTFOUND") {
438
- console.error("Hint: DNS resolution failed; double-check the host name.");
434
+ console.error(" Hint: DNS resolution failed; double-check the host name");
439
435
  }
440
436
  if (errAny.code === "ETIMEDOUT") {
441
- console.error("Hint: connection timed out; check network/firewall rules.");
437
+ console.error(" Hint: connection timed out; check network/firewall rules");
442
438
  }
443
439
  }
444
440
  process.exitCode = 1;
445
441
  } finally {
446
- try {
447
- await client.end();
448
- } catch {
449
- // ignore
442
+ if (client) {
443
+ try {
444
+ await client.end();
445
+ } catch {
446
+ // ignore
447
+ }
450
448
  }
451
449
  }
452
450
  });
@@ -109,7 +109,7 @@ 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)
@@ -132,6 +132,11 @@ program
132
132
  " If auto-generated, it is printed only on TTY by default.",
133
133
  " To print it in non-interactive mode: --print-password",
134
134
  "",
135
+ "Environment variables (libpq standard):",
136
+ " PGHOST, PGPORT, PGUSER, PGDATABASE — connection defaults",
137
+ " PGPASSWORD — admin password",
138
+ " PGAI_MON_PASSWORD — monitoring password",
139
+ "",
135
140
  "Inspect SQL without applying changes:",
136
141
  " postgresai init <conn> --print-sql",
137
142
  "",
@@ -161,7 +166,7 @@ program
161
166
  if (!shouldRedactSecrets)
162
167
  return sql;
163
168
  // Replace PASSWORD '<literal>' (handles doubled quotes inside).
164
- return sql.replace(/password\s+'(?:''|[^'])*'/gi, "password '<redacted>'");
169
+ return (0, init_1.redactPasswordsInSql)(sql);
165
170
  };
166
171
  // Offline mode: allow printing SQL without providing/using an admin connection.
167
172
  // Useful for audits/reviews; caller can provide -d/PGDATABASE and an explicit monitoring password.
@@ -176,7 +181,6 @@ program
176
181
  monitoringUser: opts.monitoringUser,
177
182
  monitoringPassword: monPassword,
178
183
  includeOptionalPermissions,
179
- roleExists: undefined,
180
184
  });
181
185
  console.log("\n--- SQL plan (offline; not connected) ---");
182
186
  console.log(`-- database: ${database}`);
@@ -209,7 +213,7 @@ program
209
213
  }
210
214
  catch (e) {
211
215
  const msg = e instanceof Error ? e.message : String(e);
212
- console.error(`✗ ${msg}`);
216
+ console.error(`Error: init: ${msg}`);
213
217
  // When connection details are missing, show full init help (options + examples).
214
218
  if (typeof msg === "string" && msg.startsWith("Connection is required.")) {
215
219
  console.error("");
@@ -223,13 +227,10 @@ program
223
227
  console.log(`Monitoring user: ${opts.monitoringUser}`);
224
228
  console.log(`Optional permissions: ${includeOptionalPermissions ? "enabled" : "skipped"}`);
225
229
  // Use native pg client instead of requiring psql to be installed
226
- const client = new pg_1.Client(adminConn.clientConfig);
230
+ let client;
227
231
  try {
232
+ client = new pg_1.Client(adminConn.clientConfig);
228
233
  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
234
  const dbRes = await client.query("select current_database() as db");
234
235
  const database = dbRes.rows?.[0]?.db;
235
236
  if (typeof database !== "string" || !database) {
@@ -273,10 +274,13 @@ program
273
274
  if (resolved.generated) {
274
275
  const canPrint = process.stdout.isTTY || !!opts.printPassword;
275
276
  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("");
277
+ // Print secrets to stderr to reduce the chance they end up in piped stdout logs.
278
+ const shellSafe = monPassword.replace(/'/g, "'\\''");
279
+ console.error("");
280
+ console.error(`Generated monitoring password for ${opts.monitoringUser} (copy/paste):`);
281
+ // Quote for shell copy/paste safety.
282
+ console.error(`PGAI_MON_PASSWORD='${shellSafe}'`);
283
+ console.error("");
280
284
  console.log("Store it securely (or rerun with --password / PGAI_MON_PASSWORD to set your own).");
281
285
  }
282
286
  else {
@@ -305,7 +309,6 @@ program
305
309
  monitoringUser: opts.monitoringUser,
306
310
  monitoringPassword: monPassword,
307
311
  includeOptionalPermissions,
308
- roleExists,
309
312
  });
310
313
  const effectivePlan = opts.resetPassword
311
314
  ? { ...plan, steps: plan.steps.filter((s) => s.name === "01.role") }
@@ -349,59 +352,56 @@ program
349
352
  if (!message || message === "[object Object]") {
350
353
  message = "Unknown error";
351
354
  }
352
- console.error(`✗ init failed: ${message}`);
355
+ console.error(`Error: init: ${message}`);
353
356
  // If this was a plan step failure, surface the step name explicitly to help users diagnose quickly.
354
357
  const stepMatch = typeof message === "string" ? message.match(/Failed at step "([^"]+)":/i) : null;
355
358
  const failedStep = stepMatch?.[1];
356
359
  if (failedStep) {
357
- console.error(`Step: ${failedStep}`);
360
+ console.error(` Step: ${failedStep}`);
358
361
  }
359
362
  if (errAny && typeof errAny === "object") {
360
363
  if (typeof errAny.code === "string" && errAny.code) {
361
- console.error(`Error code: ${errAny.code}`);
364
+ console.error(` Code: ${errAny.code}`);
362
365
  }
363
366
  if (typeof errAny.detail === "string" && errAny.detail) {
364
- console.error(`Detail: ${errAny.detail}`);
367
+ console.error(` Detail: ${errAny.detail}`);
365
368
  }
366
369
  if (typeof errAny.hint === "string" && errAny.hint) {
367
- console.error(`Hint: ${errAny.hint}`);
370
+ console.error(` Hint: ${errAny.hint}`);
368
371
  }
369
372
  }
370
373
  if (errAny && typeof errAny === "object" && typeof errAny.code === "string") {
371
374
  if (errAny.code === "42501") {
372
- console.error("");
373
- console.error("Permission error: your admin connection is not allowed to complete the setup.");
374
375
  if (failedStep === "01.role") {
375
- console.error("What failed: create/update the monitoring role (needs CREATEROLE or superuser).");
376
+ console.error(" Context: role creation/update requires CREATEROLE or superuser");
376
377
  }
377
378
  else if (failedStep === "02.permissions") {
378
- console.error("What failed: grant required permissions / create view / set role search_path.");
379
+ console.error(" Context: grants/view/search_path require sufficient GRANT/DDL privileges");
379
380
  }
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).");
381
+ console.error(" Fix: connect as a superuser (or a role with CREATEROLE and sufficient GRANT privileges)");
382
+ console.error(" Fix: on managed Postgres, use the provider's admin/master user");
383
+ console.error(" Tip: run with --print-sql to review the exact SQL plan");
386
384
  }
387
385
  if (errAny.code === "ECONNREFUSED") {
388
- console.error("Hint: check host/port and ensure Postgres is reachable from this machine.");
386
+ console.error(" Hint: check host/port and ensure Postgres is reachable from this machine");
389
387
  }
390
388
  if (errAny.code === "ENOTFOUND") {
391
- console.error("Hint: DNS resolution failed; double-check the host name.");
389
+ console.error(" Hint: DNS resolution failed; double-check the host name");
392
390
  }
393
391
  if (errAny.code === "ETIMEDOUT") {
394
- console.error("Hint: connection timed out; check network/firewall rules.");
392
+ console.error(" Hint: connection timed out; check network/firewall rules");
395
393
  }
396
394
  }
397
395
  process.exitCode = 1;
398
396
  }
399
397
  finally {
400
- try {
401
- await client.end();
402
- }
403
- catch {
404
- // ignore
398
+ if (client) {
399
+ try {
400
+ await client.end();
401
+ }
402
+ catch {
403
+ // ignore
404
+ }
405
405
  }
406
406
  }
407
407
  });