postgresai 0.16.0-dev.1 → 0.16.0-dev.11

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.
@@ -6,43 +6,25 @@ var __getProtoOf = Object.getPrototypeOf;
6
6
  var __defProp = Object.defineProperty;
7
7
  var __getOwnPropNames = Object.getOwnPropertyNames;
8
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
9
- function __accessProp(key) {
10
- return this[key];
11
- }
12
- var __toESMCache_node;
13
- var __toESMCache_esm;
14
9
  var __toESM = (mod, isNodeMode, target) => {
15
- var canCache = mod != null && typeof mod === "object";
16
- if (canCache) {
17
- var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
18
- var cached = cache.get(mod);
19
- if (cached)
20
- return cached;
21
- }
22
10
  target = mod != null ? __create(__getProtoOf(mod)) : {};
23
11
  const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
24
12
  for (let key of __getOwnPropNames(mod))
25
13
  if (!__hasOwnProp.call(to, key))
26
14
  __defProp(to, key, {
27
- get: __accessProp.bind(mod, key),
15
+ get: () => mod[key],
28
16
  enumerable: true
29
17
  });
30
- if (canCache)
31
- cache.set(mod, to);
32
18
  return to;
33
19
  };
34
20
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
35
- var __returnValue = (v) => v;
36
- function __exportSetter(name, newValue) {
37
- this[name] = __returnValue.bind(null, newValue);
38
- }
39
21
  var __export = (target, all) => {
40
22
  for (var name in all)
41
23
  __defProp(target, name, {
42
24
  get: all[name],
43
25
  enumerable: true,
44
26
  configurable: true,
45
- set: __exportSetter.bind(all, name)
27
+ set: (newValue) => all[name] = () => newValue
46
28
  });
47
29
  };
48
30
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
@@ -1020,7 +1002,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1020
1002
  this._exitCallback = (err) => {
1021
1003
  if (err.code !== "commander.executeSubCommandAsync") {
1022
1004
  throw err;
1023
- }
1005
+ } else {}
1024
1006
  };
1025
1007
  }
1026
1008
  return this;
@@ -13106,7 +13088,7 @@ var require_formats = __commonJS((exports) => {
13106
13088
  }
13107
13089
  var TIME = /^(\d\d):(\d\d):(\d\d(?:\.\d+)?)(z|([+-])(\d\d)(?::?(\d\d))?)?$/i;
13108
13090
  function getTime(strictTimeZone) {
13109
- return function time3(str2) {
13091
+ return function time(str2) {
13110
13092
  const matches = TIME.exec(str2);
13111
13093
  if (!matches)
13112
13094
  return false;
@@ -13323,43 +13305,92 @@ var require_dist2 = __commonJS((exports, module) => {
13323
13305
  var exports_util2 = {};
13324
13306
  __export(exports_util2, {
13325
13307
  resolveBaseUrls: () => resolveBaseUrls2,
13308
+ requestTimeoutSignal: () => requestTimeoutSignal2,
13309
+ redactTextSecrets: () => redactTextSecrets2,
13310
+ redactSecretsForLog: () => redactSecretsForLog2,
13311
+ redactSecrets: () => redactSecrets2,
13326
13312
  normalizeBaseUrl: () => normalizeBaseUrl2,
13327
13313
  maskSecret: () => maskSecret2,
13328
- formatHttpError: () => formatHttpError2
13314
+ isRetryableHttpStatus: () => isRetryableHttpStatus2,
13315
+ isFetchTimeout: () => isFetchTimeout2,
13316
+ formatHttpError: () => formatHttpError2,
13317
+ describeFetchError: () => describeFetchError2,
13318
+ HttpStatusError: () => HttpStatusError2,
13319
+ HttpRequestTimeoutError: () => HttpRequestTimeoutError2,
13320
+ DEFAULT_HTTP_REQUEST_TIMEOUT_MS: () => DEFAULT_HTTP_REQUEST_TIMEOUT_MS2
13329
13321
  });
13330
13322
  function isHtmlContent2(text) {
13331
13323
  const trimmed = text.trim();
13332
13324
  return trimmed.startsWith("<!DOCTYPE") || trimmed.startsWith("<html") || trimmed.startsWith("<HTML");
13333
13325
  }
13334
- function formatHttpError2(operation, status, responseBody) {
13335
- const statusMessage = HTTP_STATUS_MESSAGES2[status] || "Request failed";
13336
- let errMsg = `${operation}: HTTP ${status} - ${statusMessage}`;
13326
+ function formatHttpError2(operation, status, responseBody, statusText) {
13327
+ const generic = HTTP_STATUS_MESSAGES2[status] || "Request failed";
13337
13328
  const remediation = status === 401 ? `
13338
13329
  ${AUTH_REMEDIATION_HINT2}` : "";
13339
- if (responseBody) {
13340
- if (isHtmlContent2(responseBody)) {
13341
- return errMsg + remediation;
13342
- }
13330
+ let bodyMessage;
13331
+ let bodyDetails;
13332
+ let bodyCode;
13333
+ let bodyHint;
13334
+ if (responseBody && !isHtmlContent2(responseBody)) {
13343
13335
  try {
13344
13336
  const errObj = JSON.parse(responseBody);
13345
- const message = errObj.message || errObj.error || errObj.detail;
13346
- if (message && typeof message === "string") {
13347
- errMsg += `
13348
- ${message}`;
13349
- } else {
13350
- errMsg += `
13351
- ${JSON.stringify(errObj, null, 2)}`;
13337
+ const message = errObj.message ?? errObj.error;
13338
+ if (typeof message === "string" && message.trim().length > 0) {
13339
+ bodyMessage = redactTextSecrets2(message.trim());
13340
+ }
13341
+ const details = errObj.details ?? errObj.detail;
13342
+ if (typeof details === "string" && details.trim().length > 0) {
13343
+ bodyDetails = redactTextSecrets2(details.trim());
13344
+ }
13345
+ if (typeof errObj.code === "string" && errObj.code.trim().length > 0) {
13346
+ bodyCode = errObj.code.trim();
13347
+ }
13348
+ if (typeof errObj.hint === "string" && errObj.hint.trim().length > 0) {
13349
+ bodyHint = redactTextSecrets2(errObj.hint.trim());
13350
+ }
13351
+ if (bodyMessage === undefined && bodyDetails === undefined && bodyCode === undefined && bodyHint === undefined) {
13352
+ bodyDetails = redactSecretsForLog2(JSON.stringify(errObj));
13352
13353
  }
13353
13354
  } catch {
13354
13355
  const trimmed = responseBody.trim();
13355
13356
  if (trimmed.length > 0 && trimmed.length < 500) {
13356
- errMsg += `
13357
- ${trimmed}`;
13357
+ bodyDetails = redactTextSecrets2(trimmed);
13358
13358
  }
13359
13359
  }
13360
13360
  }
13361
+ const trimmedReason = statusText?.trim();
13362
+ const reasonPhrase = trimmedReason && trimmedReason !== STANDARD_REASON_PHRASES2[status] && trimmedReason !== generic ? trimmedReason : undefined;
13363
+ const safeReasonPhrase = reasonPhrase ? redactTextSecrets2(reasonPhrase) : undefined;
13364
+ const headline = bodyMessage ?? safeReasonPhrase ?? generic;
13365
+ const codeSuffix = bodyCode && !headline.includes(bodyCode) ? ` (${bodyCode})` : "";
13366
+ let errMsg = `${operation}: HTTP ${status} - ${headline}${codeSuffix}`;
13367
+ if (bodyDetails && bodyDetails !== headline) {
13368
+ errMsg += `
13369
+ ${bodyDetails}`;
13370
+ }
13371
+ if (bodyHint) {
13372
+ errMsg += `
13373
+ Hint: ${bodyHint}`;
13374
+ }
13361
13375
  return errMsg + remediation;
13362
13376
  }
13377
+ function describeFetchError2(operation, url, err) {
13378
+ const cause = err?.cause;
13379
+ const detail = cause?.code || cause?.message || (err instanceof Error && err.message ? err.message : String(err));
13380
+ return `${operation}: could not reach ${url} (${detail})`;
13381
+ }
13382
+ function isRetryableHttpStatus2(status) {
13383
+ return status >= 500 || status === 429;
13384
+ }
13385
+ function requestTimeoutSignal2(timeoutMs) {
13386
+ const requested = typeof timeoutMs === "number" && Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : DEFAULT_HTTP_REQUEST_TIMEOUT_MS2;
13387
+ const bounded = Math.max(1, Math.min(DEFAULT_HTTP_REQUEST_TIMEOUT_MS2, Math.floor(requested)));
13388
+ return { signal: AbortSignal.timeout(bounded), timeoutMs: bounded };
13389
+ }
13390
+ function isFetchTimeout2(err) {
13391
+ const name = err?.name;
13392
+ return name === "AbortError" || name === "TimeoutError";
13393
+ }
13363
13394
  function maskSecret2(secret) {
13364
13395
  if (!secret)
13365
13396
  return "";
@@ -13369,6 +13400,35 @@ function maskSecret2(secret) {
13369
13400
  return `${secret.slice(0, 4)}${"*".repeat(secret.length - 8)}${secret.slice(-4)}`;
13370
13401
  return `${secret.slice(0, Math.min(12, secret.length - 8))}${"*".repeat(Math.max(4, secret.length - 16))}${secret.slice(-4)}`;
13371
13402
  }
13403
+ function isSensitiveLogKey2(key) {
13404
+ const normalized = key.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[-\s]+/g, "_").toLowerCase();
13405
+ return /^(?:password|passwd|db_(?:pass|password)|conn_?str|secret|token|api_key|private_key|access_key|access_token|refresh_token|auth|auth_key|auth_token|authorization|credentials?|dsn)$/.test(normalized);
13406
+ }
13407
+ function redactSecrets2(value) {
13408
+ if (Array.isArray(value)) {
13409
+ return value.map(redactSecrets2);
13410
+ }
13411
+ if (value && typeof value === "object") {
13412
+ const out = {};
13413
+ for (const [key, child] of Object.entries(value)) {
13414
+ out[key] = isSensitiveLogKey2(key) && child != null ? "[REDACTED]" : redactSecrets2(child);
13415
+ }
13416
+ return out;
13417
+ }
13418
+ return value;
13419
+ }
13420
+ function redactSecretsForLog2(text) {
13421
+ let parsed;
13422
+ try {
13423
+ parsed = JSON.parse(text);
13424
+ } catch {
13425
+ return redactTextSecrets2(text);
13426
+ }
13427
+ return JSON.stringify(redactSecrets2(parsed));
13428
+ }
13429
+ function redactTextSecrets2(text) {
13430
+ return text.replace(TEXT_URL_USERINFO2, "$1:[REDACTED]@").replace(TEXT_SENSITIVE_PAIR2, "$1[REDACTED]");
13431
+ }
13372
13432
  function normalizeBaseUrl2(value) {
13373
13433
  const trimmed = (value || "").replace(/\/$/, "");
13374
13434
  try {
@@ -13391,7 +13451,7 @@ function resolveBaseUrls2(opts, cfg, defaults2 = {}) {
13391
13451
  storageBaseUrl: normalizeBaseUrl2(storageCandidate)
13392
13452
  };
13393
13453
  }
13394
- var HTTP_STATUS_MESSAGES2, AUTH_REMEDIATION_HINT2 = "Run 'postgresai auth' to (re)authenticate, or set/update PGAI_API_KEY.";
13454
+ var HTTP_STATUS_MESSAGES2, AUTH_REMEDIATION_HINT2 = "Run 'postgresai auth' to (re)authenticate, or set/update PGAI_API_KEY.", STANDARD_REASON_PHRASES2, HttpStatusError2, DEFAULT_HTTP_REQUEST_TIMEOUT_MS2 = 25000, HttpRequestTimeoutError2, TEXT_URL_USERINFO2, TEXT_SENSITIVE_PAIR2;
13395
13455
  var init_util = __esm(() => {
13396
13456
  HTTP_STATUS_MESSAGES2 = {
13397
13457
  400: "Bad Request",
@@ -13405,6 +13465,36 @@ var init_util = __esm(() => {
13405
13465
  503: "Service Unavailable - server temporarily unavailable",
13406
13466
  504: "Gateway Timeout - server temporarily unavailable"
13407
13467
  };
13468
+ STANDARD_REASON_PHRASES2 = {
13469
+ 400: "Bad Request",
13470
+ 401: "Unauthorized",
13471
+ 403: "Forbidden",
13472
+ 404: "Not Found",
13473
+ 408: "Request Timeout",
13474
+ 409: "Conflict",
13475
+ 413: "Payload Too Large",
13476
+ 429: "Too Many Requests",
13477
+ 500: "Internal Server Error",
13478
+ 502: "Bad Gateway",
13479
+ 503: "Service Unavailable",
13480
+ 504: "Gateway Timeout"
13481
+ };
13482
+ HttpStatusError2 = class HttpStatusError2 extends Error {
13483
+ status;
13484
+ constructor(message, status) {
13485
+ super(message);
13486
+ this.name = "HttpStatusError";
13487
+ this.status = status;
13488
+ }
13489
+ };
13490
+ HttpRequestTimeoutError2 = class HttpRequestTimeoutError2 extends Error {
13491
+ constructor(operation, timeoutMs) {
13492
+ super(`${operation}: request timed out after ${timeoutMs}ms`);
13493
+ this.name = "HttpRequestTimeoutError";
13494
+ }
13495
+ };
13496
+ TEXT_URL_USERINFO2 = /(\b[a-z][a-z0-9+.-]*:\/\/[^\s/:@]+):[^\s/@]+@/gi;
13497
+ TEXT_SENSITIVE_PAIR2 = /((?:password|passwd|db[-_]?pass|connstr|secret|token|api[-_]?key|private[-_]?key|access[-_]?key|cred(?:ential)?s?|dsn)["']?\s*[:=]\s*)("[^"]*"|'[^']*'|[^\s,;&]+)/gi;
13408
13498
  });
13409
13499
 
13410
13500
  // node_modules/commander/esm.mjs
@@ -13425,7 +13515,7 @@ var {
13425
13515
  // package.json
13426
13516
  var package_default = {
13427
13517
  name: "postgresai",
13428
- version: "0.16.0-dev.1",
13518
+ version: "0.16.0-dev.11",
13429
13519
  description: "postgres_ai CLI",
13430
13520
  license: "Apache-2.0",
13431
13521
  private: false,
@@ -16256,7 +16346,7 @@ var Result = import_lib.default.Result;
16256
16346
  var TypeOverrides = import_lib.default.TypeOverrides;
16257
16347
  var defaults = import_lib.default.defaults;
16258
16348
  // package.json
16259
- var version = "0.16.0-dev.1";
16349
+ var version = "0.16.0-dev.11";
16260
16350
  var package_default2 = {
16261
16351
  name: "postgresai",
16262
16352
  version,
@@ -16398,35 +16488,105 @@ function isHtmlContent(text) {
16398
16488
  return trimmed.startsWith("<!DOCTYPE") || trimmed.startsWith("<html") || trimmed.startsWith("<HTML");
16399
16489
  }
16400
16490
  var AUTH_REMEDIATION_HINT = "Run 'postgresai auth' to (re)authenticate, or set/update PGAI_API_KEY.";
16401
- function formatHttpError(operation, status, responseBody) {
16402
- const statusMessage = HTTP_STATUS_MESSAGES[status] || "Request failed";
16403
- let errMsg = `${operation}: HTTP ${status} - ${statusMessage}`;
16491
+ var STANDARD_REASON_PHRASES = {
16492
+ 400: "Bad Request",
16493
+ 401: "Unauthorized",
16494
+ 403: "Forbidden",
16495
+ 404: "Not Found",
16496
+ 408: "Request Timeout",
16497
+ 409: "Conflict",
16498
+ 413: "Payload Too Large",
16499
+ 429: "Too Many Requests",
16500
+ 500: "Internal Server Error",
16501
+ 502: "Bad Gateway",
16502
+ 503: "Service Unavailable",
16503
+ 504: "Gateway Timeout"
16504
+ };
16505
+ function formatHttpError(operation, status, responseBody, statusText) {
16506
+ const generic = HTTP_STATUS_MESSAGES[status] || "Request failed";
16404
16507
  const remediation = status === 401 ? `
16405
16508
  ${AUTH_REMEDIATION_HINT}` : "";
16406
- if (responseBody) {
16407
- if (isHtmlContent(responseBody)) {
16408
- return errMsg + remediation;
16409
- }
16509
+ let bodyMessage;
16510
+ let bodyDetails;
16511
+ let bodyCode;
16512
+ let bodyHint;
16513
+ if (responseBody && !isHtmlContent(responseBody)) {
16410
16514
  try {
16411
16515
  const errObj = JSON.parse(responseBody);
16412
- const message = errObj.message || errObj.error || errObj.detail;
16413
- if (message && typeof message === "string") {
16414
- errMsg += `
16415
- ${message}`;
16416
- } else {
16417
- errMsg += `
16418
- ${JSON.stringify(errObj, null, 2)}`;
16516
+ const message = errObj.message ?? errObj.error;
16517
+ if (typeof message === "string" && message.trim().length > 0) {
16518
+ bodyMessage = redactTextSecrets(message.trim());
16519
+ }
16520
+ const details = errObj.details ?? errObj.detail;
16521
+ if (typeof details === "string" && details.trim().length > 0) {
16522
+ bodyDetails = redactTextSecrets(details.trim());
16523
+ }
16524
+ if (typeof errObj.code === "string" && errObj.code.trim().length > 0) {
16525
+ bodyCode = errObj.code.trim();
16526
+ }
16527
+ if (typeof errObj.hint === "string" && errObj.hint.trim().length > 0) {
16528
+ bodyHint = redactTextSecrets(errObj.hint.trim());
16529
+ }
16530
+ if (bodyMessage === undefined && bodyDetails === undefined && bodyCode === undefined && bodyHint === undefined) {
16531
+ bodyDetails = redactSecretsForLog(JSON.stringify(errObj));
16419
16532
  }
16420
16533
  } catch {
16421
16534
  const trimmed = responseBody.trim();
16422
16535
  if (trimmed.length > 0 && trimmed.length < 500) {
16423
- errMsg += `
16424
- ${trimmed}`;
16536
+ bodyDetails = redactTextSecrets(trimmed);
16425
16537
  }
16426
16538
  }
16427
16539
  }
16540
+ const trimmedReason = statusText?.trim();
16541
+ const reasonPhrase = trimmedReason && trimmedReason !== STANDARD_REASON_PHRASES[status] && trimmedReason !== generic ? trimmedReason : undefined;
16542
+ const safeReasonPhrase = reasonPhrase ? redactTextSecrets(reasonPhrase) : undefined;
16543
+ const headline = bodyMessage ?? safeReasonPhrase ?? generic;
16544
+ const codeSuffix = bodyCode && !headline.includes(bodyCode) ? ` (${bodyCode})` : "";
16545
+ let errMsg = `${operation}: HTTP ${status} - ${headline}${codeSuffix}`;
16546
+ if (bodyDetails && bodyDetails !== headline) {
16547
+ errMsg += `
16548
+ ${bodyDetails}`;
16549
+ }
16550
+ if (bodyHint) {
16551
+ errMsg += `
16552
+ Hint: ${bodyHint}`;
16553
+ }
16428
16554
  return errMsg + remediation;
16429
16555
  }
16556
+ function describeFetchError(operation, url, err) {
16557
+ const cause = err?.cause;
16558
+ const detail = cause?.code || cause?.message || (err instanceof Error && err.message ? err.message : String(err));
16559
+ return `${operation}: could not reach ${url} (${detail})`;
16560
+ }
16561
+
16562
+ class HttpStatusError extends Error {
16563
+ status;
16564
+ constructor(message, status) {
16565
+ super(message);
16566
+ this.name = "HttpStatusError";
16567
+ this.status = status;
16568
+ }
16569
+ }
16570
+ function isRetryableHttpStatus(status) {
16571
+ return status >= 500 || status === 429;
16572
+ }
16573
+ var DEFAULT_HTTP_REQUEST_TIMEOUT_MS = 25000;
16574
+
16575
+ class HttpRequestTimeoutError extends Error {
16576
+ constructor(operation, timeoutMs) {
16577
+ super(`${operation}: request timed out after ${timeoutMs}ms`);
16578
+ this.name = "HttpRequestTimeoutError";
16579
+ }
16580
+ }
16581
+ function requestTimeoutSignal(timeoutMs) {
16582
+ const requested = typeof timeoutMs === "number" && Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : DEFAULT_HTTP_REQUEST_TIMEOUT_MS;
16583
+ const bounded = Math.max(1, Math.min(DEFAULT_HTTP_REQUEST_TIMEOUT_MS, Math.floor(requested)));
16584
+ return { signal: AbortSignal.timeout(bounded), timeoutMs: bounded };
16585
+ }
16586
+ function isFetchTimeout(err) {
16587
+ const name = err?.name;
16588
+ return name === "AbortError" || name === "TimeoutError";
16589
+ }
16430
16590
  function maskSecret(secret) {
16431
16591
  if (!secret)
16432
16592
  return "";
@@ -16436,6 +16596,37 @@ function maskSecret(secret) {
16436
16596
  return `${secret.slice(0, 4)}${"*".repeat(secret.length - 8)}${secret.slice(-4)}`;
16437
16597
  return `${secret.slice(0, Math.min(12, secret.length - 8))}${"*".repeat(Math.max(4, secret.length - 16))}${secret.slice(-4)}`;
16438
16598
  }
16599
+ function isSensitiveLogKey(key) {
16600
+ const normalized = key.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[-\s]+/g, "_").toLowerCase();
16601
+ return /^(?:password|passwd|db_(?:pass|password)|conn_?str|secret|token|api_key|private_key|access_key|access_token|refresh_token|auth|auth_key|auth_token|authorization|credentials?|dsn)$/.test(normalized);
16602
+ }
16603
+ function redactSecrets(value) {
16604
+ if (Array.isArray(value)) {
16605
+ return value.map(redactSecrets);
16606
+ }
16607
+ if (value && typeof value === "object") {
16608
+ const out = {};
16609
+ for (const [key, child] of Object.entries(value)) {
16610
+ out[key] = isSensitiveLogKey(key) && child != null ? "[REDACTED]" : redactSecrets(child);
16611
+ }
16612
+ return out;
16613
+ }
16614
+ return value;
16615
+ }
16616
+ function redactSecretsForLog(text) {
16617
+ let parsed;
16618
+ try {
16619
+ parsed = JSON.parse(text);
16620
+ } catch {
16621
+ return redactTextSecrets(text);
16622
+ }
16623
+ return JSON.stringify(redactSecrets(parsed));
16624
+ }
16625
+ var TEXT_URL_USERINFO = /(\b[a-z][a-z0-9+.-]*:\/\/[^\s/:@]+):[^\s/@]+@/gi;
16626
+ var TEXT_SENSITIVE_PAIR = /((?:password|passwd|db[-_]?pass|connstr|secret|token|api[-_]?key|private[-_]?key|access[-_]?key|cred(?:ential)?s?|dsn)["']?\s*[:=]\s*)("[^"]*"|'[^']*'|[^\s,;&]+)/gi;
16627
+ function redactTextSecrets(text) {
16628
+ return text.replace(TEXT_URL_USERINFO, "$1:[REDACTED]@").replace(TEXT_SENSITIVE_PAIR, "$1[REDACTED]");
16629
+ }
16439
16630
  function normalizeBaseUrl(value) {
16440
16631
  const trimmed = (value || "").replace(/\/$/, "");
16441
16632
  try {
@@ -20973,7 +21164,7 @@ function finalize(ctx, schema2) {
20973
21164
  result.$schema = "http://json-schema.org/draft-07/schema#";
20974
21165
  } else if (ctx.target === "draft-04") {
20975
21166
  result.$schema = "http://json-schema.org/draft-04/schema#";
20976
- } else if (ctx.target === "openapi-3.0") {}
21167
+ } else if (ctx.target === "openapi-3.0") {} else {}
20977
21168
  if (ctx.external?.uri) {
20978
21169
  const id = ctx.external.registry.get(schema2)?.id;
20979
21170
  if (!id)
@@ -21192,7 +21383,7 @@ var literalProcessor = (schema2, ctx, json2, _params) => {
21192
21383
  if (val === undefined) {
21193
21384
  if (ctx.unrepresentable === "throw") {
21194
21385
  throw new Error("Literal `undefined` cannot be represented in JSON Schema");
21195
- }
21386
+ } else {}
21196
21387
  } else if (typeof val === "bigint") {
21197
21388
  if (ctx.unrepresentable === "throw") {
21198
21389
  throw new Error("BigInt literals cannot be represented in JSON Schema");
@@ -25801,6 +25992,352 @@ function renderMarkdownForTerminal(md) {
25801
25992
  `);
25802
25993
  }
25803
25994
 
25995
+ // lib/joe.ts
25996
+ var DEFAULT_BUDGET_MS = 25000;
25997
+ var DEFAULT_POLL_INTERVAL_MS = 800;
25998
+ async function callRpc(params) {
25999
+ const { apiKey, apiBaseUrl, fn, body, operation, debug } = params;
26000
+ if (!apiKey) {
26001
+ throw new Error("API key is required");
26002
+ }
26003
+ const base = normalizeBaseUrl(apiBaseUrl);
26004
+ const url = new URL(`${base}/rpc/${fn}`);
26005
+ const payload = JSON.stringify(body);
26006
+ const headers = {
26007
+ "access-token": apiKey,
26008
+ "Content-Type": "application/json",
26009
+ Connection: "close"
26010
+ };
26011
+ if (debug) {
26012
+ const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
26013
+ console.error(`Debug: POST URL: ${url.toString()}`);
26014
+ console.error(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
26015
+ console.error(`Debug: Request body: ${redactSecretsForLog(payload)}`);
26016
+ }
26017
+ let response;
26018
+ const requestTimeout = requestTimeoutSignal(params.timeoutMs);
26019
+ try {
26020
+ response = await fetch(url.toString(), {
26021
+ method: "POST",
26022
+ headers,
26023
+ body: payload,
26024
+ signal: requestTimeout.signal
26025
+ });
26026
+ } catch (err) {
26027
+ if (isFetchTimeout(err)) {
26028
+ throw new HttpRequestTimeoutError(operation, requestTimeout.timeoutMs);
26029
+ }
26030
+ throw new Error(describeFetchError(operation, base, err));
26031
+ }
26032
+ const text = await response.text();
26033
+ if (debug) {
26034
+ console.error(`Debug: Response status: ${response.status}`);
26035
+ console.error(`Debug: Response body: ${redactSecretsForLog(text)}`);
26036
+ }
26037
+ if (!response.ok) {
26038
+ throw new HttpStatusError(formatHttpError(operation, response.status, text, response.statusText), response.status);
26039
+ }
26040
+ try {
26041
+ return JSON.parse(text);
26042
+ } catch {
26043
+ throw new Error(`${operation}: failed to parse response: ${redactSecretsForLog(text)}`);
26044
+ }
26045
+ }
26046
+ async function startCommand(params) {
26047
+ const { apiKey, apiBaseUrl, instanceId, command, debug, timeoutMs } = params;
26048
+ if (!String(command ?? "").trim()) {
26049
+ throw new Error("command text is required");
26050
+ }
26051
+ const commandId = await callRpc({
26052
+ apiKey,
26053
+ apiBaseUrl,
26054
+ fn: "joe_command_run",
26055
+ body: { instance_id: instanceId, command },
26056
+ operation: "Failed to run Joe command",
26057
+ debug,
26058
+ timeoutMs
26059
+ });
26060
+ if (typeof commandId !== "string" || !/^[0-9]+$/.test(commandId)) {
26061
+ throw new Error(`Failed to run Joe command: expected a command id string, got: ${redactSecretsForLog(JSON.stringify(commandId))}`);
26062
+ }
26063
+ return commandId;
26064
+ }
26065
+ async function getCommandOutput(params) {
26066
+ const { apiKey, apiBaseUrl, commandId, debug, timeoutMs } = params;
26067
+ if (!commandId) {
26068
+ throw new Error("commandId is required");
26069
+ }
26070
+ return callRpc({
26071
+ apiKey,
26072
+ apiBaseUrl,
26073
+ fn: "joe_command_output",
26074
+ body: { command_id: commandId },
26075
+ operation: "Failed to fetch command output",
26076
+ debug,
26077
+ timeoutMs
26078
+ });
26079
+ }
26080
+ function preserveIntegerId(value) {
26081
+ if (typeof value === "number")
26082
+ return value;
26083
+ const parsed = Number(value);
26084
+ return Number.isSafeInteger(parsed) ? parsed : value;
26085
+ }
26086
+ function normalizeProjectRow(row) {
26087
+ return {
26088
+ project_id: preserveIntegerId(row.project_id ?? 0),
26089
+ alias: row.alias ?? null,
26090
+ name: row.name ?? null,
26091
+ joe_ready: Boolean(row.joe_ready ?? false),
26092
+ tunnel: Boolean(row.tunnel ?? false),
26093
+ instance_id: row.instance_id == null ? null : preserveIntegerId(row.instance_id),
26094
+ dblab_instance_id: row.dblab_instance_id == null ? null : preserveIntegerId(row.dblab_instance_id)
26095
+ };
26096
+ }
26097
+ async function listProjects(params) {
26098
+ const { apiKey, apiBaseUrl, orgId, debug } = params;
26099
+ const body = {};
26100
+ if (typeof orgId === "number") {
26101
+ body.org_id = orgId;
26102
+ }
26103
+ const rows = await callRpc({
26104
+ apiKey,
26105
+ apiBaseUrl,
26106
+ fn: "projects_list",
26107
+ body,
26108
+ operation: "Failed to list projects",
26109
+ debug
26110
+ });
26111
+ if (!Array.isArray(rows)) {
26112
+ return [];
26113
+ }
26114
+ return rows.map(normalizeProjectRow);
26115
+ }
26116
+ function isNumericProjectRef(ref) {
26117
+ return /^[0-9]+$/.test(ref.trim());
26118
+ }
26119
+ async function resolveJoeInstanceId(params) {
26120
+ const ref = String(params.project ?? "").trim();
26121
+ if (!ref) {
26122
+ throw new Error("project is required (--project <id|alias>)");
26123
+ }
26124
+ const projects = await listProjects({
26125
+ apiKey: params.apiKey,
26126
+ apiBaseUrl: params.apiBaseUrl,
26127
+ orgId: params.orgId,
26128
+ debug: params.debug
26129
+ });
26130
+ const needle = ref.toLowerCase();
26131
+ const match = isNumericProjectRef(ref) ? projects.find((p) => String(p.project_id) === String(preserveIntegerId(ref))) : projects.find((p) => p.alias !== null && p.alias.toLowerCase() === needle || p.name !== null && p.name.toLowerCase() === needle);
26132
+ if (!match) {
26133
+ throw new Error(`Project not found for id/alias/name '${ref}'. Run 'pgai projects' to see available projects.`);
26134
+ }
26135
+ if (match.instance_id == null) {
26136
+ throw new Error(`Project '${ref}' has no Joe instance. Run 'pgai projects' to see which projects have Joe ready.`);
26137
+ }
26138
+ return match.instance_id;
26139
+ }
26140
+ var DESCRIBE_VARIANTS = [
26141
+ "\\d",
26142
+ "\\d+",
26143
+ "\\dt",
26144
+ "\\dt+",
26145
+ "\\di",
26146
+ "\\di+",
26147
+ "\\l",
26148
+ "\\l+",
26149
+ "\\dv",
26150
+ "\\dv+",
26151
+ "\\dm",
26152
+ "\\dm+"
26153
+ ];
26154
+ function buildJoeCommandText(command, input = {}) {
26155
+ const arg = String(input.arg ?? "").trim();
26156
+ switch (command) {
26157
+ case "plan":
26158
+ case "explain":
26159
+ case "exec":
26160
+ case "hypo": {
26161
+ if (!arg) {
26162
+ throw new Error(`${command} requires an argument`);
26163
+ }
26164
+ return `${command} ${arg}`;
26165
+ }
26166
+ case "activity":
26167
+ case "reset":
26168
+ return command;
26169
+ case "terminate": {
26170
+ if (!/^[1-9][0-9]*$/.test(arg)) {
26171
+ throw new Error("pid must be a positive integer");
26172
+ }
26173
+ return `terminate ${arg}`;
26174
+ }
26175
+ case "describe": {
26176
+ if (!arg) {
26177
+ throw new Error("describe requires an object name");
26178
+ }
26179
+ const variant = String(input.variant ?? "\\d").trim();
26180
+ if (!DESCRIBE_VARIANTS.includes(variant)) {
26181
+ throw new Error(`Unsupported describe variant '${variant}'. Supported: ${DESCRIBE_VARIANTS.join(" ")}`);
26182
+ }
26183
+ return `${variant} ${arg}`;
26184
+ }
26185
+ }
26186
+ }
26187
+ var defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
26188
+ async function runCommand(params) {
26189
+ const { apiKey, apiBaseUrl, instanceId, command, debug } = params;
26190
+ const budgetMs = typeof params.budgetMs === "number" && Number.isFinite(params.budgetMs) && params.budgetMs >= 0 ? params.budgetMs : DEFAULT_BUDGET_MS;
26191
+ const pollIntervalMs = params.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
26192
+ const now = params.now ?? Date.now;
26193
+ const sleep = params.sleep ?? defaultSleep;
26194
+ const commandId = await startCommand({ apiKey, apiBaseUrl, instanceId, command, debug });
26195
+ const deadline = now() + budgetMs;
26196
+ let status = "pending";
26197
+ const remainingRequestMs = () => Math.max(1, Math.min(DEFAULT_HTTP_REQUEST_TIMEOUT_MS, deadline - now()));
26198
+ if (now() >= deadline) {
26199
+ return { commandId, status, output: null, budgetExpired: true };
26200
+ }
26201
+ for (;; ) {
26202
+ let output;
26203
+ try {
26204
+ output = await getCommandOutput({
26205
+ apiKey,
26206
+ apiBaseUrl,
26207
+ commandId,
26208
+ debug,
26209
+ timeoutMs: remainingRequestMs()
26210
+ });
26211
+ } catch (err) {
26212
+ if (err instanceof HttpRequestTimeoutError) {
26213
+ return { commandId, status, output: null, budgetExpired: true };
26214
+ }
26215
+ if (err instanceof HttpStatusError && isRetryableHttpStatus(err.status)) {
26216
+ if (now() >= deadline) {
26217
+ return { commandId, status, output: null, budgetExpired: true };
26218
+ }
26219
+ await sleep(pollIntervalMs);
26220
+ continue;
26221
+ }
26222
+ throw err;
26223
+ }
26224
+ status = output.status;
26225
+ if (status === "ok" || status === "error") {
26226
+ return { commandId, status, output, budgetExpired: false };
26227
+ }
26228
+ if (now() >= deadline) {
26229
+ return { commandId, status, output: null, budgetExpired: true };
26230
+ }
26231
+ await sleep(pollIntervalMs);
26232
+ }
26233
+ }
26234
+ async function executeJoeCommand(params) {
26235
+ const commandText = buildJoeCommandText(params.command, params.input);
26236
+ let instanceId;
26237
+ const directRef = String(params.instanceId ?? "").trim();
26238
+ if (directRef) {
26239
+ if (!/^[0-9]+$/.test(directRef)) {
26240
+ throw new Error("instanceId must be a numeric Joe instance id");
26241
+ }
26242
+ instanceId = directRef;
26243
+ } else if (String(params.project ?? "").trim()) {
26244
+ instanceId = await resolveJoeInstanceId({
26245
+ apiKey: params.apiKey,
26246
+ apiBaseUrl: params.apiBaseUrl,
26247
+ project: String(params.project),
26248
+ orgId: params.orgId,
26249
+ debug: params.debug
26250
+ });
26251
+ } else {
26252
+ throw new Error("either instanceId or project is required");
26253
+ }
26254
+ const outcome = await runCommand({
26255
+ apiKey: params.apiKey,
26256
+ apiBaseUrl: params.apiBaseUrl,
26257
+ instanceId,
26258
+ command: commandText,
26259
+ budgetMs: params.budgetMs,
26260
+ pollIntervalMs: params.pollIntervalMs,
26261
+ debug: params.debug,
26262
+ now: params.now,
26263
+ sleep: params.sleep
26264
+ });
26265
+ return { ...outcome, command: params.command, instanceId, commandText };
26266
+ }
26267
+ function clientSidePlanFlags(planJson) {
26268
+ const flags = [];
26269
+ const walk = (node) => {
26270
+ if (!node || typeof node !== "object") {
26271
+ return;
26272
+ }
26273
+ const nodeType = node["Node Type"];
26274
+ if (nodeType === "Seq Scan") {
26275
+ const rel = node["Relation Name"];
26276
+ flags.push(`client-side: Seq Scan${rel ? ` on ${rel}` : ""} — no index serves this predicate; consider adding one.`);
26277
+ }
26278
+ if (Array.isArray(node.Plans)) {
26279
+ for (const child of node.Plans) {
26280
+ walk(child);
26281
+ }
26282
+ }
26283
+ };
26284
+ if (Array.isArray(planJson)) {
26285
+ for (const entry of planJson) {
26286
+ if (entry && typeof entry === "object") {
26287
+ walk(entry.Plan ?? entry);
26288
+ }
26289
+ }
26290
+ return flags;
26291
+ }
26292
+ if (planJson && typeof planJson === "object") {
26293
+ const root = planJson;
26294
+ walk(root.Plan ?? planJson);
26295
+ }
26296
+ return flags;
26297
+ }
26298
+ function formatJoeOutput(output) {
26299
+ const lines = [];
26300
+ const section = (value, label) => {
26301
+ if (value == null || value.trim() === "")
26302
+ return;
26303
+ if (lines.length > 0)
26304
+ lines.push("");
26305
+ if (label)
26306
+ lines.push(`${label}:`);
26307
+ lines.push(value);
26308
+ };
26309
+ section(output.response);
26310
+ section(output.plan_text, "plan");
26311
+ const flags = clientSidePlanFlags(output.plan_json).map((flag) => `⚑ ${flag}`);
26312
+ if (flags.length > 0) {
26313
+ lines.push(...flags);
26314
+ }
26315
+ section(output.plan_execution_text, "execution plan (EXPLAIN ANALYZE)");
26316
+ section(output.stats, "stats");
26317
+ section(output.recommendations, "recommendations");
26318
+ if (output.queryid) {
26319
+ if (lines.length > 0)
26320
+ lines.push("");
26321
+ lines.push(`(queryid ${output.queryid})`);
26322
+ }
26323
+ return lines.join(`
26324
+ `);
26325
+ }
26326
+ function formatProjectsTable(projects) {
26327
+ const header = ["PROJECT_ID", "ALIAS", "PROJECT", "JOE", "TUNNEL"];
26328
+ const rows = projects.map((p) => [
26329
+ String(p.project_id),
26330
+ p.alias ?? "-",
26331
+ p.name ?? "-",
26332
+ p.joe_ready ? "ready" : "no",
26333
+ p.tunnel ? "yes" : "no"
26334
+ ]);
26335
+ const widths = header.map((h, i2) => Math.max(h.length, ...rows.map((r) => r[i2].length), 0));
26336
+ const pad = (cells) => cells.map((c, i2) => c.padEnd(i2 === cells.length - 1 ? 0 : widths[i2])).join(" ").trimEnd();
26337
+ return [pad(header), ...rows.map(pad)].join(`
26338
+ `);
26339
+ }
26340
+
25804
26341
  // bin/postgres-ai.ts
25805
26342
  init_util();
25806
26343
 
@@ -29452,10 +29989,6 @@ async function verifyInitSetup(params) {
29452
29989
  }
29453
29990
  }
29454
29991
  }
29455
- const explainFnRes = await params.client.query("select has_function_privilege($1, 'postgres_ai.explain_generic(text, text, text)', 'EXECUTE') as ok", [role]);
29456
- if (!explainFnRes.rows?.[0]?.ok) {
29457
- missingRequired.push("EXECUTE on postgres_ai.explain_generic(text, text, text)");
29458
- }
29459
29992
  const tableDescribeFnRes = await params.client.query("select has_function_privilege($1, 'postgres_ai.table_describe(text)', 'EXECUTE') as ok", [role]);
29460
29993
  if (!tableDescribeFnRes.rows?.[0]?.ok) {
29461
29994
  missingRequired.push("EXECUTE on postgres_ai.table_describe(text)");
@@ -29522,10 +30055,28 @@ async function checkCurrentUserPermissions(client) {
29522
30055
 
29523
30056
  union all
29524
30057
 
30058
+ select
30059
+ 'postgres_ai schema exists' as permission_name,
30060
+ 'optional' as status,
30061
+ to_regnamespace('postgres_ai') is not null as granted
30062
+
30063
+ union all
30064
+
30065
+ select
30066
+ 'usage on postgres_ai schema' as permission_name,
30067
+ 'optional' as status,
30068
+ case
30069
+ when to_regnamespace('postgres_ai') is null then null
30070
+ else has_schema_privilege(current_user, 'postgres_ai', 'USAGE')
30071
+ end as granted
30072
+
30073
+ union all
30074
+
29525
30075
  select
29526
30076
  'postgres_ai.pg_statistic view exists' as permission_name,
29527
30077
  'optional' as status,
29528
30078
  case
30079
+ when to_regnamespace('postgres_ai') is null then null
29529
30080
  when not has_schema_privilege(current_user, 'postgres_ai', 'USAGE') then null
29530
30081
  else to_regclass('postgres_ai.pg_statistic') is not null
29531
30082
  end as granted
@@ -29536,6 +30087,7 @@ async function checkCurrentUserPermissions(client) {
29536
30087
  'select on postgres_ai.pg_statistic' as permission_name,
29537
30088
  'optional' as status,
29538
30089
  case
30090
+ when to_regnamespace('postgres_ai') is null then null
29539
30091
  when not has_schema_privilege(current_user, 'postgres_ai', 'USAGE') then null
29540
30092
  when to_regclass('postgres_ai.pg_statistic') is null then null
29541
30093
  else has_table_privilege(current_user, 'postgres_ai.pg_statistic', 'select')
@@ -29555,6 +30107,10 @@ async function checkCurrentUserPermissions(client) {
29555
30107
  when permission_name like 'select on pg_catalog.pg_index' then
29556
30108
  format('grant select on pg_catalog.pg_index to %I;', current_user)
29557
30109
  end
30110
+ when permission_name = 'postgres_ai schema exists' and granted = false then
30111
+ '-- run postgresai prepare-db or create the postgres_ai schema and pg_statistic view manually'
30112
+ when permission_name = 'usage on postgres_ai schema' and granted = false then
30113
+ format('grant usage on schema postgres_ai to %I;', current_user)
29558
30114
  when permission_name = 'postgres_ai.pg_statistic view exists' and granted = false then
29559
30115
  '-- create postgres_ai.pg_statistic view (see setup script)'
29560
30116
  when permission_name = 'select on postgres_ai.pg_statistic' and granted = false then
@@ -29581,6 +30137,10 @@ function formatPermissionCheckMessages(result) {
29581
30137
  const warnings = [];
29582
30138
  const errors3 = [];
29583
30139
  for (const row of result.missingOptional) {
30140
+ if (row.permission_name === "postgres_ai schema exists") {
30141
+ warnings.push("Warning: optional: postgres_ai schema not found — F004/F005 (bloat estimates) will be skipped; run prepare-db or create the view manually to enable them.");
30142
+ continue;
30143
+ }
29584
30144
  const fix = row.fix_command ? ` Fix: ${row.fix_command}` : "";
29585
30145
  warnings.push(`Warning: optional permission missing — ${row.permission_name}.${fix}`);
29586
30146
  }
@@ -29974,15 +30534,6 @@ async function verifyInitSetupViaSupabase(params) {
29974
30534
  missingRequired.push("role search_path includes postgres_ai, public and pg_catalog");
29975
30535
  }
29976
30536
  }
29977
- const explainFnExistsRes = await params.client.query("SELECT oid FROM pg_proc WHERE proname = 'explain_generic' AND pronamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'postgres_ai')", true);
29978
- if (explainFnExistsRes.rowCount === 0) {
29979
- missingRequired.push("function postgres_ai.explain_generic exists");
29980
- } else {
29981
- const explainFnRes = await params.client.query(`SELECT has_function_privilege('${escapeLiteral2(role)}', 'postgres_ai.explain_generic(text, text, text)', 'EXECUTE') as ok`, true);
29982
- if (!explainFnRes.rows?.[0]?.ok) {
29983
- missingRequired.push("EXECUTE on postgres_ai.explain_generic(text, text, text)");
29984
- }
29985
- }
29986
30537
  const tableDescribeFnExistsRes = await params.client.query("SELECT oid FROM pg_proc WHERE proname = 'table_describe' AND pronamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'postgres_ai')", true);
29987
30538
  if (tableDescribeFnExistsRes.rowCount === 0) {
29988
30539
  missingRequired.push("function postgres_ai.table_describe exists");
@@ -32013,7 +32564,7 @@ function buildCheckInfoMap() {
32013
32564
  }
32014
32565
 
32015
32566
  // lib/checkup.ts
32016
- var __dirname = "/builds/postgres-ai/postgresai/cli/lib";
32567
+ var __dirname = "/Users/nik/gitlab/postgresai-wt-joe-cli/cli/lib";
32017
32568
  var SECONDS_PER_DAY = 86400;
32018
32569
  var SECONDS_PER_HOUR = 3600;
32019
32570
  var SECONDS_PER_MINUTE = 60;
@@ -32778,12 +33329,70 @@ async function generateF003(client, nodeName) {
32778
33329
  };
32779
33330
  return report;
32780
33331
  }
33332
+ function bloatErrorStatus(err) {
33333
+ const error2 = err instanceof Error ? err.message : String(err);
33334
+ const code = typeof err === "object" && err !== null && "code" in err ? String(err.code || "") : "";
33335
+ const normalized = error2.toLowerCase();
33336
+ let reason = "query_error";
33337
+ if (code === "3F000" || normalized.includes('schema "postgres_ai" does not exist')) {
33338
+ reason = "missing_schema";
33339
+ } else if (code === "42P01" || normalized.includes('relation "postgres_ai.pg_statistic" does not exist')) {
33340
+ reason = "missing_view";
33341
+ } else if (code === "42501" || normalized.includes("permission denied")) {
33342
+ reason = "missing_grant";
33343
+ }
33344
+ return { ok: false, reason, error: error2 };
33345
+ }
33346
+ async function getBloatCheckStatus(client) {
33347
+ try {
33348
+ const result = await client.query(`
33349
+ select
33350
+ to_regnamespace('postgres_ai') is not null as schema_exists,
33351
+ case
33352
+ when to_regnamespace('postgres_ai') is null then false
33353
+ else has_schema_privilege(current_user, 'postgres_ai', 'USAGE')
33354
+ end as schema_usage,
33355
+ case
33356
+ when to_regnamespace('postgres_ai') is null then false
33357
+ when not has_schema_privilege(current_user, 'postgres_ai', 'USAGE') then false
33358
+ else to_regclass('postgres_ai.pg_statistic') is not null
33359
+ end as view_exists,
33360
+ case
33361
+ when to_regnamespace('postgres_ai') is null then false
33362
+ when not has_schema_privilege(current_user, 'postgres_ai', 'USAGE') then false
33363
+ when to_regclass('postgres_ai.pg_statistic') is null then false
33364
+ else has_table_privilege(current_user, 'postgres_ai.pg_statistic', 'SELECT')
33365
+ end as view_select
33366
+ `);
33367
+ const capability = result.rows[0] || {};
33368
+ if (!capability.schema_exists) {
33369
+ return { ok: false, reason: "missing_schema", error: 'schema "postgres_ai" does not exist' };
33370
+ }
33371
+ if (!capability.schema_usage) {
33372
+ return { ok: false, reason: "missing_grant", error: "permission denied for schema postgres_ai" };
33373
+ }
33374
+ if (!capability.view_exists) {
33375
+ return { ok: false, reason: "missing_view", error: 'relation "postgres_ai.pg_statistic" does not exist' };
33376
+ }
33377
+ if (!capability.view_select) {
33378
+ return { ok: false, reason: "missing_grant", error: "permission denied for relation postgres_ai.pg_statistic" };
33379
+ }
33380
+ return { ok: true, reason: null, error: null };
33381
+ } catch (err) {
33382
+ return bloatErrorStatus(err);
33383
+ }
33384
+ }
32781
33385
  async function generateF004(client, nodeName) {
32782
33386
  const report = createBaseReport("F004", "Autovacuum: heap bloat (estimated)", nodeName);
32783
33387
  const postgresVersion = await getPostgresVersion(client);
32784
33388
  const pgMajorVersion = parseInt(postgresVersion.server_major_ver, 10);
32785
33389
  let bloatedTables = [];
33390
+ let status = await getBloatCheckStatus(client);
32786
33391
  try {
33392
+ if (!status.ok)
33393
+ throw Object.assign(new Error(status.error || "Bloat prerequisites unavailable"), {
33394
+ code: status.reason === "missing_schema" ? "3F000" : status.reason === "missing_view" ? "42P01" : status.reason === "missing_grant" ? "42501" : undefined
33395
+ });
32787
33396
  const sql = getMetricSql(METRIC_NAMES.F004, pgMajorVersion);
32788
33397
  const bloatResult = await client.query(sql);
32789
33398
  const vacuumStatsResult = await client.query(`
@@ -32827,7 +33436,8 @@ async function generateF004(client, nodeName) {
32827
33436
  };
32828
33437
  });
32829
33438
  } catch (err) {
32830
- const errorMsg = err instanceof Error ? err.message : String(err);
33439
+ status = bloatErrorStatus(err);
33440
+ const errorMsg = status.error || "Unknown error";
32831
33441
  console.error(`[F004] Error estimating table bloat: ${errorMsg}`);
32832
33442
  if (errorMsg.includes("postgres_ai.")) {
32833
33443
  console.error(` Hint: Run "postgresai prepare-db <connection>" to create required objects.`);
@@ -32837,6 +33447,7 @@ async function generateF004(client, nodeName) {
32837
33447
  const totalCount = bloatedTables.length;
32838
33448
  const totalBloatSizeBytes = bloatedTables.reduce((sum, t) => sum + t.bloat_size, 0);
32839
33449
  const dbEntry = {
33450
+ status,
32840
33451
  bloated_tables: bloatedTables,
32841
33452
  total_count: totalCount,
32842
33453
  total_bloat_size_bytes: totalBloatSizeBytes,
@@ -32855,7 +33466,12 @@ async function generateF005(client, nodeName) {
32855
33466
  const postgresVersion = await getPostgresVersion(client);
32856
33467
  const pgMajorVersion = parseInt(postgresVersion.server_major_ver, 10);
32857
33468
  let bloatedIndexes = [];
33469
+ let status = await getBloatCheckStatus(client);
32858
33470
  try {
33471
+ if (!status.ok)
33472
+ throw Object.assign(new Error(status.error || "Bloat prerequisites unavailable"), {
33473
+ code: status.reason === "missing_schema" ? "3F000" : status.reason === "missing_view" ? "42P01" : status.reason === "missing_grant" ? "42501" : undefined
33474
+ });
32859
33475
  const sql = getMetricSql(METRIC_NAMES.F005, pgMajorVersion);
32860
33476
  const bloatResult = await client.query(sql);
32861
33477
  const vacuumStatsResult = await client.query(`
@@ -32904,7 +33520,8 @@ async function generateF005(client, nodeName) {
32904
33520
  };
32905
33521
  });
32906
33522
  } catch (err) {
32907
- const errorMsg = err instanceof Error ? err.message : String(err);
33523
+ status = bloatErrorStatus(err);
33524
+ const errorMsg = status.error || "Unknown error";
32908
33525
  console.error(`[F005] Error estimating index bloat: ${errorMsg}`);
32909
33526
  if (errorMsg.includes("postgres_ai.")) {
32910
33527
  console.error(` Hint: Run "postgresai prepare-db <connection>" to create required objects.`);
@@ -32914,6 +33531,7 @@ async function generateF005(client, nodeName) {
32914
33531
  const totalCount = bloatedIndexes.length;
32915
33532
  const totalBloatSizeBytes = bloatedIndexes.reduce((sum, idx) => sum + idx.bloat_size, 0);
32916
33533
  const dbEntry = {
33534
+ status,
32917
33535
  bloated_indexes: bloatedIndexes,
32918
33536
  total_count: totalCount,
32919
33537
  total_bloat_size_bytes: totalBloatSizeBytes,
@@ -33586,6 +34204,10 @@ function generateCheckSummary(checkId, report) {
33586
34204
  return summarizeF001(nodeData);
33587
34205
  case "F003":
33588
34206
  return summarizeF003(nodeData);
34207
+ case "F004":
34208
+ return summarizeBloat(nodeData, "table");
34209
+ case "F005":
34210
+ return summarizeBloat(nodeData, "index");
33589
34211
  case "G001":
33590
34212
  return summarizeG001(nodeData);
33591
34213
  case "G003":
@@ -33771,6 +34393,25 @@ function summarizeF003(nodeData) {
33771
34393
  }
33772
34394
  return { status: "warning", message: parts.join(", ") };
33773
34395
  }
34396
+ function summarizeBloat(nodeData, kind) {
34397
+ const data = nodeData?.data || {};
34398
+ let totalCount = 0;
34399
+ for (const dbData of Object.values(data)) {
34400
+ const dbEntry = dbData;
34401
+ if (dbEntry?.status?.ok === false) {
34402
+ const reason = String(dbEntry.status.reason || "query_error").replaceAll("_", " ");
34403
+ return { status: "warning", message: `Bloat estimate degraded: ${reason}` };
34404
+ }
34405
+ totalCount += dbEntry?.total_count || 0;
34406
+ }
34407
+ if (totalCount === 0) {
34408
+ return { status: "ok", message: `No bloated ${kind}${kind === "index" ? "es" : "s"} found` };
34409
+ }
34410
+ return {
34411
+ status: "warning",
34412
+ message: `Found ${totalCount} bloated ${kind}${totalCount === 1 ? "" : kind === "index" ? "es" : "s"}`
34413
+ };
34414
+ }
33774
34415
  function summarizeG001(nodeData) {
33775
34416
  const data = nodeData?.data || {};
33776
34417
  const settingsCount = Object.keys(data).length;
@@ -34127,9 +34768,15 @@ function prepareUploadConfig(opts, rootOpts, shouldUpload, uploadExplicitlyReque
34127
34768
  console.error("Tip: run 'postgresai auth' or pass --api-key / set PGAI_API_KEY");
34128
34769
  return null;
34129
34770
  }
34130
- console.error("Notice: no API key configured \u2014 results will NOT be uploaded to PostgresAI.");
34131
- console.error(" To upload: run 'postgresai auth login' or pass --api-key / set PGAI_API_KEY.");
34132
- console.error(" To run locally without this notice, pass --no-upload.");
34771
+ if (opts.markdown) {
34772
+ console.error("Notice: no API key configured \u2014 regular report upload is disabled.");
34773
+ console.error(" The full report JSON will still be sent to the PostgresAI API for markdown conversion.");
34774
+ console.error(" To avoid sending report data, replace --markdown with --no-upload and --json or --output.");
34775
+ } else {
34776
+ console.error("Notice: no API key configured \u2014 results will NOT be uploaded to PostgresAI.");
34777
+ console.error(" To upload: run 'postgresai auth login' or pass --api-key / set PGAI_API_KEY.");
34778
+ console.error(" To run locally without this notice, pass --no-upload.");
34779
+ }
34133
34780
  return;
34134
34781
  }
34135
34782
  const cfg = readConfig();
@@ -35354,7 +36001,7 @@ program2.command("unprepare-db [conn]").description("remove monitoring setup: dr
35354
36001
  closeReadline();
35355
36002
  }
35356
36003
  });
35357
- program2.command("checkup [checkIdOrConn] [conn]").description("generate health check reports directly from PostgreSQL (express mode)").option("--check-id <id>", `specific check to run (see list below), or ALL`).option("--node-name <name>", "node name for reports", "node-01").option("--output <path>", "output directory for JSON files").option("--upload", "upload JSON results to PostgresAI (requires API key)").option("--no-upload", "disable upload to PostgresAI").option("--project <project>", "project name or ID for remote upload (used with --upload; defaults to config defaultProject; auto-generated on first run)").option("--json", "output JSON to stdout").option("--markdown", "output markdown to stdout").addHelpText("after", [
36004
+ program2.command("checkup [checkIdOrConn] [conn]").description("generate health check reports directly from PostgreSQL (express mode)").option("--check-id <id>", `specific check to run (see list below), or ALL`).option("--node-name <name>", "node name for reports", "node-01").option("--output <path>", "output directory for JSON files").option("--upload", "upload JSON results to PostgresAI (requires API key)").option("--no-upload", "disable upload to PostgresAI").option("--project <project>", "project name or ID for remote upload (used with --upload; defaults to config defaultProject; auto-generated on first run)").option("--json", "output JSON to stdout").option("--markdown", "output markdown via PostgresAI API (transmits the full report JSON)").addHelpText("after", [
35358
36005
  "",
35359
36006
  "Available checks:",
35360
36007
  ...Object.entries(CHECK_INFO).map(([id, title]) => ` ${id}: ${title}`),
@@ -35365,7 +36012,7 @@ program2.command("checkup [checkIdOrConn] [conn]").description("generate health
35365
36012
  " postgresai checkup postgresql://user:pass@host:5432/db --check-id H002",
35366
36013
  " postgresai checkup postgresql://user:pass@host:5432/db --output ./reports",
35367
36014
  " postgresai checkup postgresql://user:pass@host:5432/db --no-upload --json",
35368
- " postgresai checkup postgresql://user:pass@host:5432/db --no-upload --markdown"
36015
+ " postgresai checkup postgresql://user:pass@host:5432/db --markdown"
35369
36016
  ].join(`
35370
36017
  `)).action(async (checkIdOrConn, connArg, opts, cmd) => {
35371
36018
  const checkIdPattern = /^[A-Z]\d{3}$/i;
@@ -35405,6 +36052,13 @@ Usage: postgresai checkup ${checkId} postgresql://user@host:5432/dbname
35405
36052
  return;
35406
36053
  }
35407
36054
  const uploadExplicitlyDisabled = opts.upload === false;
36055
+ if (uploadExplicitlyDisabled && shouldConvertMarkdown) {
36056
+ console.error("Error: --no-upload and --markdown are mutually exclusive");
36057
+ console.error("Markdown conversion is performed by the PostgresAI API and transmits the full report JSON.");
36058
+ console.error("Drop --no-upload to allow transmission, or use --json or --output for local-only output.");
36059
+ process.exitCode = 1;
36060
+ return;
36061
+ }
35408
36062
  let shouldUpload = !uploadExplicitlyDisabled;
35409
36063
  const outputPath = prepareOutputDirectory(opts.output);
35410
36064
  if (outputPath === null) {
@@ -35587,7 +36241,9 @@ Usage: postgresai checkup ${checkId} postgresql://user@host:5432/dbname
35587
36241
  console.log(`
35588
36242
  For details:`);
35589
36243
  console.log(" --json Output JSON");
35590
- console.log(" --markdown Output markdown");
36244
+ if (!uploadExplicitlyDisabled) {
36245
+ console.log(" --markdown Output markdown via PostgresAI API");
36246
+ }
35591
36247
  console.log(" --output <dir> Save to directory");
35592
36248
  }
35593
36249
  } catch (error2) {
@@ -35659,6 +36315,16 @@ function checkRunningContainers() {
35659
36315
  return { running: false, containers: [] };
35660
36316
  }
35661
36317
  }
36318
+ function planMonitoringRegistration(args) {
36319
+ const projectName = args.project?.trim() || undefined;
36320
+ if (args.instanceId) {
36321
+ return { kind: "adopt", projectName };
36322
+ }
36323
+ if (!projectName) {
36324
+ return { kind: "error-missing-project", projectName: undefined };
36325
+ }
36326
+ return { kind: "self-register", projectName };
36327
+ }
35662
36328
  async function registerMonitoringInstance(apiKey, projectName, opts) {
35663
36329
  const { apiBaseUrl } = resolveBaseUrls2(opts);
35664
36330
  const url = `${apiBaseUrl}/rpc/monitoring_instance_register`;
@@ -35666,16 +36332,19 @@ async function registerMonitoringInstance(apiKey, projectName, opts) {
35666
36332
  const instanceId = opts?.instanceId;
35667
36333
  const retries = opts?.retries ?? (instanceId ? 1 : 0);
35668
36334
  const retryDelayMs = opts?.retryDelayMs ?? 400;
36335
+ const hasProjectName = !!(projectName && projectName.trim());
35669
36336
  if (debug) {
35670
36337
  console.error(`
35671
36338
  Debug: Registering monitoring instance...`);
35672
36339
  console.error(`Debug: POST ${url}`);
35673
- console.error(`Debug: project_name=${projectName}${instanceId ? ` instance_id=${instanceId}` : ""}`);
36340
+ console.error(`Debug: ${hasProjectName ? `project_name=${projectName}` : "project_name=(omitted)"}${instanceId ? ` instance_id=${instanceId}` : ""}`);
35674
36341
  }
35675
36342
  const requestBody = {
35676
- api_token: apiKey,
35677
- project_name: projectName
36343
+ api_token: apiKey
35678
36344
  };
36345
+ if (hasProjectName) {
36346
+ requestBody.project_name = projectName;
36347
+ }
35679
36348
  if (instanceId) {
35680
36349
  requestBody.instance_id = instanceId;
35681
36350
  }
@@ -36218,8 +36887,9 @@ Searched: ${demoCandidates.join(", ")}
36218
36887
  console.log(`\u2713 Services started
36219
36888
  `);
36220
36889
  if (apiKey && !opts.demo) {
36221
- const projectName = opts.project || "postgres-ai-monitoring";
36222
36890
  const instanceId = opts.instanceId || process.env.PGAI_INSTANCE_ID;
36891
+ const plan = planMonitoringRegistration({ project: opts.project, instanceId });
36892
+ const projectName = plan.projectName;
36223
36893
  if (instanceId) {
36224
36894
  const reg = await registerMonitoringInstance(apiKey, projectName, {
36225
36895
  apiBaseUrl: globalOpts.apiBaseUrl,
@@ -36235,9 +36905,9 @@ Searched: ${demoCandidates.join(", ")}
36235
36905
  console.log(`\u2713 ${verb} monitoring instance (project: ${adoptedProject})
36236
36906
  `);
36237
36907
  } else if (reg) {
36238
- console.error(`\u26A0 Adopted provisioned instance ${instanceId} but the platform returned no project \u2014 reports will use project '${projectName}'`);
36908
+ console.error(`\u26A0 Adopted provisioned instance ${instanceId} but the platform returned no project` + (projectName ? ` \u2014 reports will use project '${projectName}'` : ` \u2014 reports will have no project until 'postgresai mon local-install' is re-run with --project <name>`));
36239
36909
  } else {
36240
- console.error(`\u26A0 Could not adopt provisioned instance ${instanceId} \u2014 reports will use project '${projectName}' until 'postgresai mon local-install' is re-run`);
36910
+ console.error(`\u26A0 Could not adopt provisioned instance ${instanceId}` + (projectName ? ` \u2014 reports will use project '${projectName}' until 'postgresai mon local-install' is re-run` : ` \u2014 reports will have no project until 'postgresai mon local-install' is re-run with --project <name>`));
36241
36911
  }
36242
36912
  const aas = await registerAasCollection(apiKey, instanceId, {
36243
36913
  grafanaPassword,
@@ -36253,6 +36923,9 @@ Searched: ${demoCandidates.join(", ")}
36253
36923
  console.error(`\u26A0 AAS auto-collection not registered (${aas.reason}); it can be enabled later by re-running 'postgresai mon local-install'
36254
36924
  `);
36255
36925
  }
36926
+ } else if (plan.kind === "error-missing-project") {
36927
+ console.error("\u2717 A project name is required for self-registration (the 'postgres-ai-monitoring' default was removed). " + "Re-run with --project <name>, or adopt a console-provisioned instance with --instance-id <uuid>.");
36928
+ process.exitCode = 1;
36256
36929
  } else {
36257
36930
  registerMonitoringInstance(apiKey, projectName, {
36258
36931
  apiBaseUrl: globalOpts.apiBaseUrl,
@@ -36785,7 +37458,7 @@ targets.command("test <name>").description("test monitoring target database conn
36785
37458
  }
36786
37459
  });
36787
37460
  var auth = program2.command("auth").description("authentication and API key management");
36788
- auth.command("login", { isDefault: true }).description("authenticate via browser (OAuth) or store API key directly").option("--set-key <key>", "store API key directly without OAuth flow").option("--port <port>", "local callback server port (default: random)", parseInt).option("--debug", "enable debug output").action(async (opts) => {
37461
+ async function runAuthLogin(opts) {
36789
37462
  if (opts.setKey) {
36790
37463
  const trimmedKey = opts.setKey.trim();
36791
37464
  if (!trimmedKey) {
@@ -36986,7 +37659,12 @@ Authentication failed: ${message}`);
36986
37659
  console.error(`Authentication error: ${message}`);
36987
37660
  process.exit(1);
36988
37661
  }
36989
- });
37662
+ }
37663
+ function configureLoginCommand(command) {
37664
+ return command.description("authenticate via browser (OAuth) or store API key directly").option("--set-key <key>", "store API key directly without OAuth flow").option("--port <port>", "local callback server port (default: random)", parseInt).option("--debug", "enable debug output");
37665
+ }
37666
+ configureLoginCommand(auth.command("login", { isDefault: true })).action(runAuthLogin);
37667
+ configureLoginCommand(program2.command("login")).action(runAuthLogin);
36990
37668
  auth.command("show-key").description("show API key (masked)").action(async () => {
36991
37669
  const cfg = readConfig();
36992
37670
  if (!cfg.apiKey) {
@@ -37889,6 +38567,177 @@ function tryParseJson(s) {
37889
38567
  return s;
37890
38568
  }
37891
38569
  }
38570
+ function printJoeOutcome(outcome, json3, budgetMs) {
38571
+ const effectiveBudgetMs = typeof budgetMs === "number" && Number.isFinite(budgetMs) ? budgetMs : DEFAULT_BUDGET_MS;
38572
+ const budgetSeconds = Math.round(effectiveBudgetMs / 1000);
38573
+ if (outcome.budgetExpired) {
38574
+ if (json3) {
38575
+ console.log(JSON.stringify({
38576
+ command_id: outcome.commandId,
38577
+ status: outcome.status,
38578
+ budget_expired: true,
38579
+ resume: `pgai joe result ${outcome.commandId}`
38580
+ }, null, 2));
38581
+ } else {
38582
+ console.log(`started ${outcome.commandId} \xB7 ${outcome.status} \xB7 budget ${budgetSeconds}s reached \u2014 resume: pgai joe result ${outcome.commandId}`);
38583
+ }
38584
+ return;
38585
+ }
38586
+ const output = outcome.output;
38587
+ if (outcome.status === "ok") {
38588
+ if (!output) {
38589
+ console.error(`command ${outcome.commandId} ok but output is empty`);
38590
+ process.exitCode = 1;
38591
+ return;
38592
+ }
38593
+ if (json3)
38594
+ console.log(JSON.stringify(output, null, 2));
38595
+ else {
38596
+ console.log(`command ${outcome.commandId} \xB7 ok`);
38597
+ const body = formatJoeOutput(output);
38598
+ if (body)
38599
+ console.log(body);
38600
+ }
38601
+ return;
38602
+ }
38603
+ if (json3 && output)
38604
+ console.log(JSON.stringify(output, null, 2));
38605
+ console.error(`command ${outcome.commandId} error: ${output?.error ?? "command failed"}`);
38606
+ process.exitCode = 1;
38607
+ }
38608
+ async function runJoeCli(command, arg, opts) {
38609
+ try {
38610
+ const rootOpts = program2.opts();
38611
+ const cfg = readConfig();
38612
+ const { apiKey } = getConfig(rootOpts);
38613
+ if (!apiKey) {
38614
+ console.error("API key is required. Run 'pgai auth' first or set --api-key.");
38615
+ process.exitCode = 1;
38616
+ return;
38617
+ }
38618
+ const projectRef = (opts.project ?? cfg.defaultProject ?? "").toString().trim();
38619
+ const instanceRef = (opts.instanceId ?? "").toString().trim();
38620
+ if (!projectRef && !instanceRef) {
38621
+ console.error("Specify --instance-id <id> (or --project <id|alias> once projects_list is available).");
38622
+ process.exitCode = 1;
38623
+ return;
38624
+ }
38625
+ const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
38626
+ if (typeof opts.budget === "number" && (!Number.isFinite(opts.budget) || opts.budget < 0)) {
38627
+ throw new Error("--budget must be a non-negative number of seconds");
38628
+ }
38629
+ const budgetMs = typeof opts.budget === "number" ? opts.budget * 1000 : undefined;
38630
+ const outcome = await executeJoeCommand({
38631
+ apiKey,
38632
+ apiBaseUrl,
38633
+ command,
38634
+ project: projectRef || undefined,
38635
+ instanceId: instanceRef || undefined,
38636
+ input: { arg, variant: opts.variant ?? null },
38637
+ orgId: cfg.orgId ?? undefined,
38638
+ budgetMs,
38639
+ debug: !!opts.debug
38640
+ });
38641
+ printJoeOutcome(outcome, !!opts.json, budgetMs);
38642
+ } catch (err) {
38643
+ const message = err instanceof Error ? err.message : String(err);
38644
+ console.error(message);
38645
+ process.exitCode = 1;
38646
+ }
38647
+ }
38648
+ function withJoeOptions(cmd) {
38649
+ return cmd.option("--instance-id <id>", "target the Joe instance id directly (skips --project resolution; the v1 path while projects_list is unavailable)").option("--project <id|alias>", "target project by numeric id OR alias/name (requires projects_list)").option("--budget <seconds>", "one-shot poll budget in seconds (default 25)", (v) => parseFloat(v)).option("--debug", "enable debug output").option("--json", "output raw JSON");
38650
+ }
38651
+ var joe = program2.command("joe").description("Joe \u2014 plan/EXPLAIN/exec queries on ephemeral DBLab clones");
38652
+ withJoeOptions(joe.command("plan <sql>").description("plan a query (EXPLAIN, plan-only \u2014 no execution; the fast/safe default)")).action(async (sql, opts) => {
38653
+ await runJoeCli("plan", sql, opts);
38654
+ });
38655
+ withJoeOptions(joe.command("explain <sql>").description("EXPLAIN + EXPLAIN ANALYZE a query (EXECUTES on the ephemeral clone)")).action(async (sql, opts) => {
38656
+ await runJoeCli("explain", sql, opts);
38657
+ });
38658
+ withJoeOptions(joe.command("exec <sql>").description("run arbitrary DDL/DML on the clone (e.g. create index, analyze)")).action(async (sql, opts) => {
38659
+ await runJoeCli("exec", sql, opts);
38660
+ });
38661
+ withJoeOptions(joe.command("hypo <args>").description("HypoPG hypothetical indexes (e.g. `hypo create index on users (email)`, `hypo desc`, `hypo reset`)")).action(async (args, opts) => {
38662
+ await runJoeCli("hypo", args, opts);
38663
+ });
38664
+ withJoeOptions(joe.command("activity").description("running-activity snapshot (pg_stat_activity) on the clone")).action(async (opts) => {
38665
+ await runJoeCli("activity", null, opts);
38666
+ });
38667
+ withJoeOptions(joe.command("terminate <pid>").description("pg_terminate_backend(pid) on the clone")).action(async (pid, opts) => {
38668
+ await runJoeCli("terminate", pid, opts);
38669
+ });
38670
+ withJoeOptions(joe.command("reset").description("reset/recreate the session's thin clone")).action(async (opts) => {
38671
+ await runJoeCli("reset", null, opts);
38672
+ });
38673
+ withJoeOptions(joe.command("describe <object>").description("\\d-family schema/relation/index metadata").option("--variant <variant>", "\\d-family variant (e.g. \\d+, \\di, \\dt)")).action(async (object4, opts) => {
38674
+ await runJoeCli("describe", object4, opts);
38675
+ });
38676
+ joe.command("result <commandId>").description("fetch a Joe command's output by id (resume a budget-expired one-shot)").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (commandId, opts) => {
38677
+ try {
38678
+ const rootOpts = program2.opts();
38679
+ const cfg = readConfig();
38680
+ const { apiKey } = getConfig(rootOpts);
38681
+ if (!apiKey) {
38682
+ console.error("API key is required. Run 'pgai auth' first or set --api-key.");
38683
+ process.exitCode = 1;
38684
+ return;
38685
+ }
38686
+ const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
38687
+ const output = await getCommandOutput({ apiKey, apiBaseUrl, commandId, debug: !!opts.debug });
38688
+ if (opts.json) {
38689
+ console.log(JSON.stringify(output, null, 2));
38690
+ if (output.status !== "ok") {
38691
+ process.exitCode = 1;
38692
+ }
38693
+ return;
38694
+ }
38695
+ if (output.status === "ok") {
38696
+ console.log(`command ${output.command_id} \xB7 ok`);
38697
+ const body = formatJoeOutput(output);
38698
+ if (body)
38699
+ console.log(body);
38700
+ } else if (output.status === "error") {
38701
+ console.error(`command ${output.command_id} error: ${output.error ?? "command failed"}`);
38702
+ process.exitCode = 1;
38703
+ } else {
38704
+ console.error(`command ${output.command_id} \xB7 ${output.status} \u2014 result is not ready`);
38705
+ process.exitCode = 1;
38706
+ }
38707
+ } catch (err) {
38708
+ const message = err instanceof Error ? err.message : String(err);
38709
+ console.error(message);
38710
+ process.exitCode = 1;
38711
+ }
38712
+ });
38713
+ program2.command("projects").description("list the org's projects (shows which have Joe ready) \u2014 org-level, not a Joe endpoint").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (opts) => {
38714
+ try {
38715
+ const rootOpts = program2.opts();
38716
+ const cfg = readConfig();
38717
+ const { apiKey } = getConfig(rootOpts);
38718
+ if (!apiKey) {
38719
+ console.error("API key is required. Run 'pgai auth' first or set --api-key.");
38720
+ process.exitCode = 1;
38721
+ return;
38722
+ }
38723
+ const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
38724
+ const projects = await listProjects({
38725
+ apiKey,
38726
+ apiBaseUrl,
38727
+ orgId: cfg.orgId ?? undefined,
38728
+ debug: !!opts.debug
38729
+ });
38730
+ if (opts.json) {
38731
+ console.log(JSON.stringify(projects, null, 2));
38732
+ } else {
38733
+ console.log(formatProjectsTable(projects));
38734
+ }
38735
+ } catch (err) {
38736
+ const message = err instanceof Error ? err.message : String(err);
38737
+ console.error(message);
38738
+ process.exitCode = 1;
38739
+ }
38740
+ });
37892
38741
  var mcp = program2.command("mcp").description("MCP server integration");
37893
38742
  mcp.command("start").description("start MCP stdio server").option("--debug", "enable debug output").action(async (opts) => {
37894
38743
  const rootOpts = program2.opts();
@@ -38018,5 +38867,6 @@ export {
38018
38867
  registerMonitoringInstance,
38019
38868
  refreshBundledComposeIfStale,
38020
38869
  readDeployedTag,
38870
+ planMonitoringRegistration,
38021
38871
  isValidComposeYaml
38022
38872
  };