postgresai 0.16.0-dev.10 → 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;
@@ -9980,8 +9962,8 @@ var require_utils3 = __commonJS((exports, module) => {
9980
9962
  }
9981
9963
  return ind;
9982
9964
  }
9983
- function removeDotSegments(path5) {
9984
- let input = path5;
9965
+ function removeDotSegments(path4) {
9966
+ let input = path4;
9985
9967
  const output = [];
9986
9968
  let nextSlash = -1;
9987
9969
  let len = 0;
@@ -10171,8 +10153,8 @@ var require_schemes = __commonJS((exports, module) => {
10171
10153
  wsComponent.secure = undefined;
10172
10154
  }
10173
10155
  if (wsComponent.resourceName) {
10174
- const [path5, query] = wsComponent.resourceName.split("?");
10175
- wsComponent.path = path5 && path5 !== "/" ? path5 : undefined;
10156
+ const [path4, query] = wsComponent.resourceName.split("?");
10157
+ wsComponent.path = path4 && path4 !== "/" ? path4 : undefined;
10176
10158
  wsComponent.query = query;
10177
10159
  wsComponent.resourceName = undefined;
10178
10160
  }
@@ -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;
@@ -13307,12 +13289,12 @@ var require_dist2 = __commonJS((exports, module) => {
13307
13289
  throw new Error(`Unknown format "${name}"`);
13308
13290
  return f;
13309
13291
  };
13310
- function addFormats(ajv, list, fs5, exportName) {
13292
+ function addFormats(ajv, list, fs4, exportName) {
13311
13293
  var _a2;
13312
13294
  var _b;
13313
13295
  (_a2 = (_b = ajv.opts.code).formats) !== null && _a2 !== undefined || (_b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`);
13314
13296
  for (const f of list)
13315
- ajv.addFormat(f, fs5[f]);
13297
+ ajv.addFormat(f, fs4[f]);
13316
13298
  }
13317
13299
  module.exports = exports = formatsPlugin;
13318
13300
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -13323,11 +13305,19 @@ 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,
13326
13310
  redactSecretsForLog: () => redactSecretsForLog2,
13311
+ redactSecrets: () => redactSecrets2,
13327
13312
  normalizeBaseUrl: () => normalizeBaseUrl2,
13328
13313
  maskSecret: () => maskSecret2,
13314
+ isRetryableHttpStatus: () => isRetryableHttpStatus2,
13315
+ isFetchTimeout: () => isFetchTimeout2,
13329
13316
  formatHttpError: () => formatHttpError2,
13330
- describeFetchError: () => describeFetchError2
13317
+ describeFetchError: () => describeFetchError2,
13318
+ HttpStatusError: () => HttpStatusError2,
13319
+ HttpRequestTimeoutError: () => HttpRequestTimeoutError2,
13320
+ DEFAULT_HTTP_REQUEST_TIMEOUT_MS: () => DEFAULT_HTTP_REQUEST_TIMEOUT_MS2
13331
13321
  });
13332
13322
  function isHtmlContent2(text) {
13333
13323
  const trimmed = text.trim();
@@ -13339,31 +13329,48 @@ function formatHttpError2(operation, status, responseBody, statusText) {
13339
13329
  ${AUTH_REMEDIATION_HINT2}` : "";
13340
13330
  let bodyMessage;
13341
13331
  let bodyDetails;
13332
+ let bodyCode;
13333
+ let bodyHint;
13342
13334
  if (responseBody && !isHtmlContent2(responseBody)) {
13343
13335
  try {
13344
13336
  const errObj = JSON.parse(responseBody);
13345
13337
  const message = errObj.message ?? errObj.error;
13346
13338
  if (typeof message === "string" && message.trim().length > 0) {
13347
- bodyMessage = message.trim();
13339
+ bodyMessage = redactTextSecrets2(message.trim());
13348
13340
  }
13349
13341
  const details = errObj.details ?? errObj.detail;
13350
13342
  if (typeof details === "string" && details.trim().length > 0) {
13351
- bodyDetails = details.trim();
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
- bodyDetails = trimmed;
13357
+ bodyDetails = redactTextSecrets2(trimmed);
13357
13358
  }
13358
13359
  }
13359
13360
  }
13360
13361
  const trimmedReason = statusText?.trim();
13361
13362
  const reasonPhrase = trimmedReason && trimmedReason !== STANDARD_REASON_PHRASES2[status] && trimmedReason !== generic ? trimmedReason : undefined;
13362
- const headline = bodyMessage ?? reasonPhrase ?? generic;
13363
- let errMsg = `${operation}: HTTP ${status} - ${headline}`;
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}`;
13364
13367
  if (bodyDetails && bodyDetails !== headline) {
13365
13368
  errMsg += `
13366
13369
  ${bodyDetails}`;
13370
+ }
13371
+ if (bodyHint) {
13372
+ errMsg += `
13373
+ Hint: ${bodyHint}`;
13367
13374
  }
13368
13375
  return errMsg + remediation;
13369
13376
  }
@@ -13372,6 +13379,18 @@ function describeFetchError2(operation, url, err) {
13372
13379
  const detail = cause?.code || cause?.message || (err instanceof Error && err.message ? err.message : String(err));
13373
13380
  return `${operation}: could not reach ${url} (${detail})`;
13374
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
+ }
13375
13394
  function maskSecret2(secret) {
13376
13395
  if (!secret)
13377
13396
  return "";
@@ -13381,27 +13400,34 @@ function maskSecret2(secret) {
13381
13400
  return `${secret.slice(0, 4)}${"*".repeat(secret.length - 8)}${secret.slice(-4)}`;
13382
13401
  return `${secret.slice(0, Math.min(12, secret.length - 8))}${"*".repeat(Math.max(4, secret.length - 16))}${secret.slice(-4)}`;
13383
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
+ }
13384
13420
  function redactSecretsForLog2(text) {
13385
13421
  let parsed;
13386
13422
  try {
13387
13423
  parsed = JSON.parse(text);
13388
13424
  } catch {
13389
- return text;
13425
+ return redactTextSecrets2(text);
13390
13426
  }
13391
- const walk = (node) => {
13392
- if (Array.isArray(node)) {
13393
- return node.map(walk);
13394
- }
13395
- if (node && typeof node === "object") {
13396
- const out = {};
13397
- for (const [key, value] of Object.entries(node)) {
13398
- out[key] = SENSITIVE_LOG_KEY2.test(key) && value != null ? "[REDACTED]" : walk(value);
13399
- }
13400
- return out;
13401
- }
13402
- return node;
13403
- };
13404
- return JSON.stringify(walk(parsed));
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]");
13405
13431
  }
13406
13432
  function normalizeBaseUrl2(value) {
13407
13433
  const trimmed = (value || "").replace(/\/$/, "");
@@ -13425,7 +13451,7 @@ function resolveBaseUrls2(opts, cfg, defaults2 = {}) {
13425
13451
  storageBaseUrl: normalizeBaseUrl2(storageCandidate)
13426
13452
  };
13427
13453
  }
13428
- var HTTP_STATUS_MESSAGES2, AUTH_REMEDIATION_HINT2 = "Run 'postgresai auth' to (re)authenticate, or set/update PGAI_API_KEY.", STANDARD_REASON_PHRASES2, SENSITIVE_LOG_KEY2;
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;
13429
13455
  var init_util = __esm(() => {
13430
13456
  HTTP_STATUS_MESSAGES2 = {
13431
13457
  400: "Bad Request",
@@ -13453,7 +13479,22 @@ var init_util = __esm(() => {
13453
13479
  503: "Service Unavailable",
13454
13480
  504: "Gateway Timeout"
13455
13481
  };
13456
- SENSITIVE_LOG_KEY2 = /password|connstr/i;
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;
13457
13498
  });
13458
13499
 
13459
13500
  // node_modules/commander/esm.mjs
@@ -13474,7 +13515,7 @@ var {
13474
13515
  // package.json
13475
13516
  var package_default = {
13476
13517
  name: "postgresai",
13477
- version: "0.16.0-dev.10",
13518
+ version: "0.16.0-dev.11",
13478
13519
  description: "postgres_ai CLI",
13479
13520
  license: "Apache-2.0",
13480
13521
  private: false,
@@ -16285,11 +16326,11 @@ var safeLoadAll = renamed("safeLoadAll", "loadAll");
16285
16326
  var safeDump = renamed("safeDump", "dump");
16286
16327
 
16287
16328
  // bin/postgres-ai.ts
16288
- import * as fs11 from "fs";
16289
- import * as path9 from "path";
16329
+ import * as fs9 from "fs";
16330
+ import * as path7 from "path";
16290
16331
  import * as os3 from "os";
16291
16332
  import { fileURLToPath as fileURLToPath2 } from "url";
16292
- import * as crypto4 from "crypto";
16333
+ import * as crypto2 from "crypto";
16293
16334
 
16294
16335
  // node_modules/pg/esm/index.mjs
16295
16336
  var import_lib = __toESM(require_lib2(), 1);
@@ -16305,7 +16346,7 @@ var Result = import_lib.default.Result;
16305
16346
  var TypeOverrides = import_lib.default.TypeOverrides;
16306
16347
  var defaults = import_lib.default.defaults;
16307
16348
  // package.json
16308
- var version = "0.16.0-dev.10";
16349
+ var version = "0.16.0-dev.11";
16309
16350
  var package_default2 = {
16310
16351
  name: "postgresai",
16311
16352
  version,
@@ -16467,31 +16508,48 @@ function formatHttpError(operation, status, responseBody, statusText) {
16467
16508
  ${AUTH_REMEDIATION_HINT}` : "";
16468
16509
  let bodyMessage;
16469
16510
  let bodyDetails;
16511
+ let bodyCode;
16512
+ let bodyHint;
16470
16513
  if (responseBody && !isHtmlContent(responseBody)) {
16471
16514
  try {
16472
16515
  const errObj = JSON.parse(responseBody);
16473
16516
  const message = errObj.message ?? errObj.error;
16474
16517
  if (typeof message === "string" && message.trim().length > 0) {
16475
- bodyMessage = message.trim();
16518
+ bodyMessage = redactTextSecrets(message.trim());
16476
16519
  }
16477
16520
  const details = errObj.details ?? errObj.detail;
16478
16521
  if (typeof details === "string" && details.trim().length > 0) {
16479
- bodyDetails = details.trim();
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));
16480
16532
  }
16481
16533
  } catch {
16482
16534
  const trimmed = responseBody.trim();
16483
16535
  if (trimmed.length > 0 && trimmed.length < 500) {
16484
- bodyDetails = trimmed;
16536
+ bodyDetails = redactTextSecrets(trimmed);
16485
16537
  }
16486
16538
  }
16487
16539
  }
16488
16540
  const trimmedReason = statusText?.trim();
16489
16541
  const reasonPhrase = trimmedReason && trimmedReason !== STANDARD_REASON_PHRASES[status] && trimmedReason !== generic ? trimmedReason : undefined;
16490
- const headline = bodyMessage ?? reasonPhrase ?? generic;
16491
- let errMsg = `${operation}: HTTP ${status} - ${headline}`;
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}`;
16492
16546
  if (bodyDetails && bodyDetails !== headline) {
16493
16547
  errMsg += `
16494
16548
  ${bodyDetails}`;
16549
+ }
16550
+ if (bodyHint) {
16551
+ errMsg += `
16552
+ Hint: ${bodyHint}`;
16495
16553
  }
16496
16554
  return errMsg + remediation;
16497
16555
  }
@@ -16500,6 +16558,35 @@ function describeFetchError(operation, url, err) {
16500
16558
  const detail = cause?.code || cause?.message || (err instanceof Error && err.message ? err.message : String(err));
16501
16559
  return `${operation}: could not reach ${url} (${detail})`;
16502
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
+ }
16503
16590
  function maskSecret(secret) {
16504
16591
  if (!secret)
16505
16592
  return "";
@@ -16509,28 +16596,36 @@ function maskSecret(secret) {
16509
16596
  return `${secret.slice(0, 4)}${"*".repeat(secret.length - 8)}${secret.slice(-4)}`;
16510
16597
  return `${secret.slice(0, Math.min(12, secret.length - 8))}${"*".repeat(Math.max(4, secret.length - 16))}${secret.slice(-4)}`;
16511
16598
  }
16512
- var SENSITIVE_LOG_KEY = /password|connstr/i;
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
+ }
16513
16616
  function redactSecretsForLog(text) {
16514
16617
  let parsed;
16515
16618
  try {
16516
16619
  parsed = JSON.parse(text);
16517
16620
  } catch {
16518
- return text;
16621
+ return redactTextSecrets(text);
16519
16622
  }
16520
- const walk = (node) => {
16521
- if (Array.isArray(node)) {
16522
- return node.map(walk);
16523
- }
16524
- if (node && typeof node === "object") {
16525
- const out = {};
16526
- for (const [key, value] of Object.entries(node)) {
16527
- out[key] = SENSITIVE_LOG_KEY.test(key) && value != null ? "[REDACTED]" : walk(value);
16528
- }
16529
- return out;
16530
- }
16531
- return node;
16532
- };
16533
- return JSON.stringify(walk(parsed));
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]");
16534
16629
  }
16535
16630
  function normalizeBaseUrl(value) {
16536
16631
  const trimmed = (value || "").replace(/\/$/, "");
@@ -17363,611 +17458,9 @@ async function fetchReportFileData(params) {
17363
17458
  }
17364
17459
  }
17365
17460
 
17366
- // lib/joe.ts
17461
+ // lib/storage.ts
17367
17462
  import * as fs3 from "fs";
17368
17463
  import * as path3 from "path";
17369
- import * as crypto from "node:crypto";
17370
- var JOE_COMMANDS = [
17371
- "plan",
17372
- "explain",
17373
- "exec",
17374
- "hypo",
17375
- "activity",
17376
- "terminate",
17377
- "reset",
17378
- "describe"
17379
- ];
17380
- var TERMINAL_STATES = new Set([
17381
- "done",
17382
- "error",
17383
- "timed_out"
17384
- ]);
17385
- var AI_ENABLED_COMMANDS = new Set([
17386
- "plan",
17387
- "explain",
17388
- "exec",
17389
- "hypo",
17390
- "activity",
17391
- "describe"
17392
- ]);
17393
- var EXECUTING_COMMANDS = new Set([
17394
- "exec",
17395
- "explain"
17396
- ]);
17397
- var DEFAULT_BUDGET_MS = 25000;
17398
- var DEFAULT_POLL_INTERVAL_MS = 800;
17399
- async function callRpc(params) {
17400
- const { apiKey, apiBaseUrl, fn, body, operation, debug } = params;
17401
- if (!apiKey) {
17402
- throw new Error("API key is required");
17403
- }
17404
- const base = normalizeBaseUrl(apiBaseUrl);
17405
- const url = new URL(`${base}/rpc/${fn}`);
17406
- const payload = JSON.stringify(body);
17407
- const headers = {
17408
- "access-token": apiKey,
17409
- "Content-Type": "application/json",
17410
- Connection: "close"
17411
- };
17412
- if (debug) {
17413
- const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
17414
- console.error(`Debug: POST URL: ${url.toString()}`);
17415
- console.error(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
17416
- console.error(`Debug: Request body: ${redactSecretsForLog(payload)}`);
17417
- }
17418
- let response;
17419
- try {
17420
- response = await fetch(url.toString(), {
17421
- method: "POST",
17422
- headers,
17423
- body: payload
17424
- });
17425
- } catch (err) {
17426
- throw new Error(describeFetchError(operation, base, err));
17427
- }
17428
- const text = await response.text();
17429
- if (debug) {
17430
- console.error(`Debug: Response status: ${response.status}`);
17431
- console.error(`Debug: Response body: ${redactSecretsForLog(text)}`);
17432
- }
17433
- if (!response.ok) {
17434
- throw new Error(formatHttpError(operation, response.status, text, response.statusText));
17435
- }
17436
- try {
17437
- return JSON.parse(text);
17438
- } catch {
17439
- throw new Error(`${operation}: failed to parse response: ${text}`);
17440
- }
17441
- }
17442
- async function submitCommand(params) {
17443
- const { apiKey, apiBaseUrl, command, projectId, sql, args, sessionId, idempotencyKey, debug } = params;
17444
- if (!JOE_COMMANDS.includes(command)) {
17445
- throw new Error(`Unknown Joe command: ${command}`);
17446
- }
17447
- const body = {
17448
- project_id: projectId,
17449
- command,
17450
- sql: sql ?? null,
17451
- args: args ?? null,
17452
- session_id: sessionId ?? null
17453
- };
17454
- if (idempotencyKey) {
17455
- body.idempotency_key = idempotencyKey;
17456
- }
17457
- return callRpc({
17458
- apiKey,
17459
- apiBaseUrl,
17460
- fn: "joe_command_submit",
17461
- body,
17462
- operation: `Failed to submit ${command} command`,
17463
- debug
17464
- });
17465
- }
17466
- async function getCommandStatus(params) {
17467
- const { apiKey, apiBaseUrl, commandId, debug } = params;
17468
- if (!commandId) {
17469
- throw new Error("commandId is required");
17470
- }
17471
- return callRpc({
17472
- apiKey,
17473
- apiBaseUrl,
17474
- fn: "joe_command_status",
17475
- body: { command_id: commandId },
17476
- operation: "Failed to fetch command status",
17477
- debug
17478
- });
17479
- }
17480
- async function getCommandResult(params) {
17481
- const { apiKey, apiBaseUrl, commandId, debug } = params;
17482
- if (!commandId) {
17483
- throw new Error("commandId is required");
17484
- }
17485
- return callRpc({
17486
- apiKey,
17487
- apiBaseUrl,
17488
- fn: "joe_command_result",
17489
- body: { command_id: commandId },
17490
- operation: "Failed to fetch command result",
17491
- debug
17492
- });
17493
- }
17494
- function normalizeProjectRow(row) {
17495
- const projectId = row.project_id ?? row.id ?? 0;
17496
- return {
17497
- project_id: Number(projectId),
17498
- alias: row.alias ?? null,
17499
- name: row.name ?? null,
17500
- label: row.label ?? null,
17501
- joe_ready: Boolean(row.joe_ready ?? row.joe_api_v2_enabled ?? false),
17502
- tunnel: Boolean(row.tunnel ?? row.tunnel_ready ?? false),
17503
- instance_id: row.instance_id == null ? null : Number(row.instance_id),
17504
- dblab_instance_id: row.dblab_instance_id == null ? null : Number(row.dblab_instance_id)
17505
- };
17506
- }
17507
- async function listProjects(params) {
17508
- const { apiKey, apiBaseUrl, orgId, debug } = params;
17509
- const body = {};
17510
- if (typeof orgId === "number") {
17511
- body.org_id = orgId;
17512
- }
17513
- const rows = await callRpc({
17514
- apiKey,
17515
- apiBaseUrl,
17516
- fn: "projects_list",
17517
- body,
17518
- operation: "Failed to list projects",
17519
- debug
17520
- });
17521
- if (!Array.isArray(rows)) {
17522
- return [];
17523
- }
17524
- return rows.map(normalizeProjectRow);
17525
- }
17526
- function isNumericProjectRef(ref) {
17527
- return /^[0-9]+$/.test(ref.trim());
17528
- }
17529
- async function resolveProjectId(params) {
17530
- const ref = String(params.project ?? "").trim();
17531
- if (!ref) {
17532
- throw new Error("project is required (--project <id|alias>)");
17533
- }
17534
- if (isNumericProjectRef(ref)) {
17535
- return Number(ref);
17536
- }
17537
- const projects = await listProjects({
17538
- apiKey: params.apiKey,
17539
- apiBaseUrl: params.apiBaseUrl,
17540
- orgId: params.orgId,
17541
- debug: params.debug
17542
- });
17543
- const needle = ref.toLowerCase();
17544
- const match = projects.find((p) => p.alias !== null && p.alias.toLowerCase() === needle || p.name !== null && p.name.toLowerCase() === needle || p.label !== null && p.label.toLowerCase() === needle);
17545
- if (!match) {
17546
- throw new Error(`Project not found for alias/name '${ref}'. Run 'pgai projects' to see available projects.`);
17547
- }
17548
- return match.project_id;
17549
- }
17550
- function sessionStorePath(dir) {
17551
- return path3.join(dir ?? getConfigDir2(), "joe-sessions.json");
17552
- }
17553
- function readSessionStore(dir) {
17554
- const file = sessionStorePath(dir);
17555
- if (!fs3.existsSync(file)) {
17556
- return {};
17557
- }
17558
- try {
17559
- const parsed = JSON.parse(fs3.readFileSync(file, "utf8"));
17560
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
17561
- return parsed;
17562
- }
17563
- } catch {}
17564
- return {};
17565
- }
17566
- function readStoredSessionId(projectId, dir) {
17567
- const store = readSessionStore(dir);
17568
- const value = store[String(projectId)];
17569
- return typeof value === "string" && value.length > 0 ? value : null;
17570
- }
17571
- function writeStoredSessionId(projectId, sessionId, dir) {
17572
- const baseDir = dir ?? getConfigDir2();
17573
- if (!fs3.existsSync(baseDir)) {
17574
- fs3.mkdirSync(baseDir, { recursive: true, mode: 448 });
17575
- }
17576
- const store = readSessionStore(dir);
17577
- store[String(projectId)] = sessionId;
17578
- fs3.writeFileSync(sessionStorePath(dir), JSON.stringify(store, null, 2) + `
17579
- `, { mode: 384 });
17580
- }
17581
- function clearStoredSessionId(projectId, dir) {
17582
- const file = sessionStorePath(dir);
17583
- if (!fs3.existsSync(file)) {
17584
- return;
17585
- }
17586
- const store = readSessionStore(dir);
17587
- if (String(projectId) in store) {
17588
- delete store[String(projectId)];
17589
- fs3.writeFileSync(file, JSON.stringify(store, null, 2) + `
17590
- `, { mode: 384 });
17591
- }
17592
- }
17593
- function computeIdempotencyKey(command, projectId, sql, args) {
17594
- if (EXECUTING_COMMANDS.has(command)) {
17595
- const canonical = `${projectId}
17596
- ${command}
17597
- ${sql ?? ""}
17598
- ${JSON.stringify(args ?? null)}`;
17599
- return `joe-${crypto.createHash("sha256").update(canonical).digest("hex")}`;
17600
- }
17601
- return `joe-${crypto.randomUUID()}`;
17602
- }
17603
- var defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
17604
- async function runCommand(params) {
17605
- const {
17606
- apiKey,
17607
- apiBaseUrl,
17608
- command,
17609
- projectId,
17610
- sql,
17611
- args,
17612
- sessionId,
17613
- debug
17614
- } = params;
17615
- const budgetMs = typeof params.budgetMs === "number" && Number.isFinite(params.budgetMs) ? params.budgetMs : DEFAULT_BUDGET_MS;
17616
- const pollIntervalMs = params.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
17617
- const now = params.now ?? Date.now;
17618
- const sleep = params.sleep ?? defaultSleep;
17619
- const idempotencyKey = params.idempotencyKey ?? computeIdempotencyKey(command, projectId, sql, args);
17620
- const submitted = await submitCommand({
17621
- apiKey,
17622
- apiBaseUrl,
17623
- command,
17624
- projectId,
17625
- sql,
17626
- args,
17627
- sessionId,
17628
- idempotencyKey,
17629
- debug
17630
- });
17631
- const commandId = submitted.command_id;
17632
- const newSessionId = submitted.session_id ?? sessionId ?? null;
17633
- const deadline = now() + budgetMs;
17634
- let status = submitted.status;
17635
- while (true) {
17636
- const statusResp = await getCommandStatus({ apiKey, apiBaseUrl, commandId, debug });
17637
- status = statusResp.status;
17638
- if (TERMINAL_STATES.has(status)) {
17639
- break;
17640
- }
17641
- if (now() >= deadline) {
17642
- return { commandId, sessionId: newSessionId, status, result: null, budgetExpired: true };
17643
- }
17644
- await sleep(pollIntervalMs);
17645
- }
17646
- const result = await getCommandResult({ apiKey, apiBaseUrl, commandId, debug });
17647
- return { commandId, sessionId: newSessionId, status, result, budgetExpired: false };
17648
- }
17649
- async function executeJoeCommand(params) {
17650
- const projectId = await resolveProjectId({
17651
- apiKey: params.apiKey,
17652
- apiBaseUrl: params.apiBaseUrl,
17653
- project: params.project,
17654
- orgId: params.orgId,
17655
- debug: params.debug
17656
- });
17657
- let sessionId;
17658
- if (params.newSession) {
17659
- clearStoredSessionId(projectId, params.sessionDir);
17660
- sessionId = null;
17661
- } else if (params.session) {
17662
- sessionId = String(params.session);
17663
- } else {
17664
- sessionId = readStoredSessionId(projectId, params.sessionDir);
17665
- }
17666
- const outcome = await runCommand({
17667
- apiKey: params.apiKey,
17668
- apiBaseUrl: params.apiBaseUrl,
17669
- command: params.command,
17670
- projectId,
17671
- sql: params.sql,
17672
- args: params.args,
17673
- sessionId,
17674
- budgetMs: params.budgetMs,
17675
- pollIntervalMs: params.pollIntervalMs,
17676
- debug: params.debug,
17677
- now: params.now,
17678
- sleep: params.sleep
17679
- });
17680
- if (outcome.sessionId) {
17681
- writeStoredSessionId(projectId, outcome.sessionId, params.sessionDir);
17682
- }
17683
- return { ...outcome, command: params.command, projectId };
17684
- }
17685
-
17686
- // lib/dblab.ts
17687
- function isNumericProjectRef2(ref) {
17688
- return /^[0-9]+$/.test(ref.trim());
17689
- }
17690
- async function resolveDblabInstanceId(params) {
17691
- const { apiKey, apiBaseUrl, orgId, debug } = params;
17692
- if (!apiKey) {
17693
- throw new Error("API key is required");
17694
- }
17695
- const ref = String(params.project ?? "").trim();
17696
- if (!ref) {
17697
- throw new Error("project is required (--project <id|alias>)");
17698
- }
17699
- let projects;
17700
- try {
17701
- projects = await listProjects({ apiKey, apiBaseUrl, orgId, debug });
17702
- } catch (err) {
17703
- const message = err instanceof Error ? err.message : String(err);
17704
- throw new Error(`Failed to resolve project's DBLab instance: ${message}`);
17705
- }
17706
- const numeric = isNumericProjectRef2(ref);
17707
- const needle = ref.toLowerCase();
17708
- const match = projects.find((p) => {
17709
- if (numeric) {
17710
- return String(p.project_id) === ref;
17711
- }
17712
- return p.alias !== null && p.alias.toLowerCase() === needle || p.name !== null && p.name.toLowerCase() === needle || p.label !== null && p.label.toLowerCase() === needle;
17713
- });
17714
- if (!match) {
17715
- throw new Error(`No DBLab instance found for project '${ref}'. Run 'pgai projects' to see available projects.`);
17716
- }
17717
- if (match.dblab_instance_id == null) {
17718
- throw new Error(`Project '${ref}' has no active DBLab instance. Register a DBLab instance for it in the Console first.`);
17719
- }
17720
- return String(match.dblab_instance_id);
17721
- }
17722
- async function callDblabApi(params) {
17723
- const { apiKey, apiBaseUrl, instanceId, action, method, data, operation, debug } = params;
17724
- if (!apiKey) {
17725
- throw new Error("API key is required");
17726
- }
17727
- if (!instanceId) {
17728
- throw new Error("instanceId is required");
17729
- }
17730
- const base = normalizeBaseUrl(apiBaseUrl);
17731
- const url = new URL(`${base}/rpc/dblab_api_call`);
17732
- const bodyObj = {
17733
- instance_id: instanceId,
17734
- action,
17735
- method
17736
- };
17737
- if (data !== undefined) {
17738
- bodyObj.data = data;
17739
- }
17740
- const body = JSON.stringify(bodyObj);
17741
- const headers = {
17742
- "access-token": apiKey,
17743
- "Content-Type": "application/json",
17744
- Connection: "close"
17745
- };
17746
- if (debug) {
17747
- const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
17748
- console.error(`Debug: POST URL: ${url.toString()}`);
17749
- console.error(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
17750
- console.error(`Debug: Request body: ${redactSecretsForLog(body)}`);
17751
- }
17752
- let response;
17753
- try {
17754
- response = await fetch(url.toString(), { method: "POST", headers, body });
17755
- } catch (err) {
17756
- throw new Error(describeFetchError(operation, base, err));
17757
- }
17758
- const text = await response.text();
17759
- if (debug) {
17760
- console.error(`Debug: Response status: ${response.status}`);
17761
- console.error(`Debug: Response body: ${redactSecretsForLog(text)}`);
17762
- }
17763
- if (!response.ok) {
17764
- throw new Error(formatHttpError(operation, response.status, text, response.statusText));
17765
- }
17766
- if (text.trim() === "") {
17767
- return null;
17768
- }
17769
- try {
17770
- return JSON.parse(text);
17771
- } catch {
17772
- throw new Error(`${operation}: failed to parse response: ${text}`);
17773
- }
17774
- }
17775
- async function createClone(params) {
17776
- const { apiKey, apiBaseUrl, instanceId, cloneId, branch, snapshotId, dbUser, dbPassword, isProtected, debug } = params;
17777
- const data = { protected: Boolean(isProtected) };
17778
- if (cloneId)
17779
- data.id = cloneId;
17780
- if (branch)
17781
- data.branch = branch;
17782
- if (snapshotId)
17783
- data.snapshot = { id: snapshotId };
17784
- if (dbUser && dbPassword)
17785
- data.db = { username: dbUser, password: dbPassword };
17786
- return callDblabApi({
17787
- apiKey,
17788
- apiBaseUrl,
17789
- instanceId,
17790
- action: "/clone",
17791
- method: "post",
17792
- data,
17793
- operation: "Failed to create clone",
17794
- debug
17795
- });
17796
- }
17797
- async function listClones(params) {
17798
- const { apiKey, apiBaseUrl, instanceId, debug } = params;
17799
- return callDblabApi({
17800
- apiKey,
17801
- apiBaseUrl,
17802
- instanceId,
17803
- action: "/clones",
17804
- method: "get",
17805
- operation: "Failed to list clones",
17806
- debug
17807
- });
17808
- }
17809
- async function getClone(params) {
17810
- const { apiKey, apiBaseUrl, instanceId, cloneId, debug } = params;
17811
- if (!cloneId)
17812
- throw new Error("cloneId is required");
17813
- return callDblabApi({
17814
- apiKey,
17815
- apiBaseUrl,
17816
- instanceId,
17817
- action: `/clone/${encodeURIComponent(cloneId)}`,
17818
- method: "get",
17819
- operation: "Failed to get clone",
17820
- debug
17821
- });
17822
- }
17823
- async function resetClone(params) {
17824
- const { apiKey, apiBaseUrl, instanceId, cloneId, snapshotId, latest, debug } = params;
17825
- if (!cloneId)
17826
- throw new Error("cloneId is required");
17827
- const data = { latest: latest ?? !snapshotId };
17828
- if (snapshotId)
17829
- data.snapshotID = snapshotId;
17830
- return callDblabApi({
17831
- apiKey,
17832
- apiBaseUrl,
17833
- instanceId,
17834
- action: `/clone/${encodeURIComponent(cloneId)}/reset`,
17835
- method: "post",
17836
- data,
17837
- operation: "Failed to reset clone",
17838
- debug
17839
- });
17840
- }
17841
- async function destroyClone(params) {
17842
- const { apiKey, apiBaseUrl, instanceId, cloneId, debug } = params;
17843
- if (!cloneId)
17844
- throw new Error("cloneId is required");
17845
- return callDblabApi({
17846
- apiKey,
17847
- apiBaseUrl,
17848
- instanceId,
17849
- action: `/clone/${encodeURIComponent(cloneId)}`,
17850
- method: "delete",
17851
- operation: "Failed to destroy clone",
17852
- debug
17853
- });
17854
- }
17855
- async function listBranches(params) {
17856
- const { apiKey, apiBaseUrl, instanceId, debug } = params;
17857
- return callDblabApi({
17858
- apiKey,
17859
- apiBaseUrl,
17860
- instanceId,
17861
- action: "/branches",
17862
- method: "get",
17863
- operation: "Failed to list branches",
17864
- debug
17865
- });
17866
- }
17867
- async function createBranch(params) {
17868
- const { apiKey, apiBaseUrl, instanceId, branchName, baseBranch, snapshotId, debug } = params;
17869
- if (!branchName)
17870
- throw new Error("branchName is required");
17871
- const data = { branchName };
17872
- if (baseBranch)
17873
- data.baseBranch = baseBranch;
17874
- if (snapshotId)
17875
- data.snapshotID = snapshotId;
17876
- return callDblabApi({
17877
- apiKey,
17878
- apiBaseUrl,
17879
- instanceId,
17880
- action: "/branch",
17881
- method: "post",
17882
- data,
17883
- operation: "Failed to create branch",
17884
- debug
17885
- });
17886
- }
17887
- async function deleteBranch(params) {
17888
- const { apiKey, apiBaseUrl, instanceId, branchName, debug } = params;
17889
- if (!branchName)
17890
- throw new Error("branchName is required");
17891
- return callDblabApi({
17892
- apiKey,
17893
- apiBaseUrl,
17894
- instanceId,
17895
- action: `/branch/${encodeURIComponent(branchName)}`,
17896
- method: "delete",
17897
- operation: "Failed to delete branch",
17898
- debug
17899
- });
17900
- }
17901
- async function branchLog(params) {
17902
- const { apiKey, apiBaseUrl, instanceId, branchName, debug } = params;
17903
- if (!branchName)
17904
- throw new Error("branchName is required");
17905
- return callDblabApi({
17906
- apiKey,
17907
- apiBaseUrl,
17908
- instanceId,
17909
- action: `/branch/${encodeURIComponent(branchName)}/log`,
17910
- method: "get",
17911
- operation: "Failed to fetch branch log",
17912
- debug
17913
- });
17914
- }
17915
- async function listSnapshots(params) {
17916
- const { apiKey, apiBaseUrl, instanceId, branchName, dataset, debug } = params;
17917
- const qs = new URLSearchParams;
17918
- const branch = branchName?.trim();
17919
- if (branch)
17920
- qs.append("branch", branch);
17921
- if (dataset)
17922
- qs.append("dataset", dataset);
17923
- const action = `/snapshots${qs.toString() ? `?${qs.toString()}` : ""}`;
17924
- return callDblabApi({
17925
- apiKey,
17926
- apiBaseUrl,
17927
- instanceId,
17928
- action,
17929
- method: "get",
17930
- operation: "Failed to list snapshots",
17931
- debug
17932
- });
17933
- }
17934
- async function createSnapshot(params) {
17935
- const { apiKey, apiBaseUrl, instanceId, cloneId, message, debug } = params;
17936
- if (!cloneId)
17937
- throw new Error("cloneId is required");
17938
- const data = { cloneID: cloneId };
17939
- if (message)
17940
- data.message = message;
17941
- return callDblabApi({
17942
- apiKey,
17943
- apiBaseUrl,
17944
- instanceId,
17945
- action: "/branch/snapshot",
17946
- method: "post",
17947
- data,
17948
- operation: "Failed to create snapshot",
17949
- debug
17950
- });
17951
- }
17952
- async function destroySnapshot(params) {
17953
- const { apiKey, apiBaseUrl, instanceId, snapshotId, force, debug } = params;
17954
- if (!snapshotId)
17955
- throw new Error("snapshotId is required");
17956
- const action = `/snapshot/${snapshotId}?force=${Boolean(force)}`;
17957
- return callDblabApi({
17958
- apiKey,
17959
- apiBaseUrl,
17960
- instanceId,
17961
- action,
17962
- method: "delete",
17963
- operation: "Failed to destroy snapshot",
17964
- debug
17965
- });
17966
- }
17967
-
17968
- // lib/storage.ts
17969
- import * as fs4 from "fs";
17970
- import * as path4 from "path";
17971
17464
  var MAX_UPLOAD_SIZE = 500 * 1024 * 1024;
17972
17465
  var MAX_DOWNLOAD_SIZE = 500 * 1024 * 1024;
17973
17466
  var MIME_TYPES = {
@@ -18008,11 +17501,11 @@ async function uploadFile(params) {
18008
17501
  if (!filePath) {
18009
17502
  throw new Error("filePath is required");
18010
17503
  }
18011
- const resolvedPath = path4.resolve(filePath);
18012
- if (!fs4.existsSync(resolvedPath)) {
17504
+ const resolvedPath = path3.resolve(filePath);
17505
+ if (!fs3.existsSync(resolvedPath)) {
18013
17506
  throw new Error(`File not found: ${resolvedPath}`);
18014
17507
  }
18015
- const stat = fs4.statSync(resolvedPath);
17508
+ const stat = fs3.statSync(resolvedPath);
18016
17509
  if (!stat.isFile()) {
18017
17510
  throw new Error(`Not a file: ${resolvedPath}`);
18018
17511
  }
@@ -18024,9 +17517,9 @@ async function uploadFile(params) {
18024
17517
  console.error("Warning: storage URL uses HTTP — API key will be sent unencrypted");
18025
17518
  }
18026
17519
  const url = `${base}/upload`;
18027
- const fileBuffer = fs4.readFileSync(resolvedPath);
18028
- const fileName = path4.basename(resolvedPath);
18029
- const ext = path4.extname(fileName).toLowerCase();
17520
+ const fileBuffer = fs3.readFileSync(resolvedPath);
17521
+ const fileName = path3.basename(resolvedPath);
17522
+ const ext = path3.extname(fileName).toLowerCase();
18030
17523
  const mimeType = MIME_TYPES[ext] || "application/octet-stream";
18031
17524
  const formData = new FormData;
18032
17525
  formData.append("file", new Blob([fileBuffer], { type: mimeType }), fileName);
@@ -18085,15 +17578,15 @@ async function downloadFile(params) {
18085
17578
  const relativePath = fileUrl.startsWith("/") ? fileUrl : `/${fileUrl}`;
18086
17579
  fullUrl = `${base}${relativePath}`;
18087
17580
  }
18088
- const urlFilename = path4.basename(new URL(fullUrl).pathname);
17581
+ const urlFilename = path3.basename(new URL(fullUrl).pathname);
18089
17582
  if (!urlFilename) {
18090
17583
  throw new Error("Cannot derive filename from URL; please specify --output");
18091
17584
  }
18092
- const saveTo = outputPath ? path4.resolve(outputPath) : path4.resolve(urlFilename);
17585
+ const saveTo = outputPath ? path3.resolve(outputPath) : path3.resolve(urlFilename);
18093
17586
  if (!outputPath) {
18094
- const normalizedSave = path4.normalize(saveTo);
18095
- const cwd = path4.normalize(process.cwd());
18096
- if (normalizedSave !== cwd && !normalizedSave.startsWith(cwd + path4.sep)) {
17587
+ const normalizedSave = path3.normalize(saveTo);
17588
+ const cwd = path3.normalize(process.cwd());
17589
+ if (normalizedSave !== cwd && !normalizedSave.startsWith(cwd + path3.sep)) {
18097
17590
  throw new Error("Derived output path escapes current directory; please specify --output");
18098
17591
  }
18099
17592
  }
@@ -18125,11 +17618,11 @@ async function downloadFile(params) {
18125
17618
  }
18126
17619
  const arrayBuffer = await response.arrayBuffer();
18127
17620
  const buffer = Buffer.from(arrayBuffer);
18128
- const parentDir = path4.dirname(saveTo);
18129
- if (!fs4.existsSync(parentDir)) {
18130
- fs4.mkdirSync(parentDir, { recursive: true });
17621
+ const parentDir = path3.dirname(saveTo);
17622
+ if (!fs3.existsSync(parentDir)) {
17623
+ fs3.mkdirSync(parentDir, { recursive: true });
18131
17624
  }
18132
- fs4.writeFileSync(saveTo, buffer);
17625
+ fs3.writeFileSync(saveTo, buffer);
18133
17626
  return {
18134
17627
  savedTo: saveTo,
18135
17628
  size: buffer.length,
@@ -18141,9 +17634,9 @@ function buildMarkdownLink(fileUrl, storageBaseUrl, filename) {
18141
17634
  const base = normalizeBaseUrl(storageBaseUrl);
18142
17635
  const normalizedFileUrl = fileUrl.startsWith("/") ? fileUrl : `/${fileUrl}`;
18143
17636
  const fullUrl = fileUrl.startsWith("http://") || fileUrl.startsWith("https://") ? fileUrl : `${base}${normalizedFileUrl}`;
18144
- const name = filename || path4.basename(new URL(fullUrl).pathname);
17637
+ const name = filename || path3.basename(new URL(fullUrl).pathname);
18145
17638
  const safeName = name.replace(/[\[\]()]/g, "\\$&");
18146
- const ext = path4.extname(name).toLowerCase();
17639
+ const ext = path3.extname(name).toLowerCase();
18147
17640
  if (IMAGE_EXTENSIONS.has(ext)) {
18148
17641
  return `![${safeName}](${fullUrl})`;
18149
17642
  }
@@ -18431,10 +17924,10 @@ function mergeDefs(...defs) {
18431
17924
  function cloneDef(schema2) {
18432
17925
  return mergeDefs(schema2._zod.def);
18433
17926
  }
18434
- function getElementAtPath(obj, path5) {
18435
- if (!path5)
17927
+ function getElementAtPath(obj, path4) {
17928
+ if (!path4)
18436
17929
  return obj;
18437
- return path5.reduce((acc, key) => acc?.[key], obj);
17930
+ return path4.reduce((acc, key) => acc?.[key], obj);
18438
17931
  }
18439
17932
  function promiseAllObject(promisesObj) {
18440
17933
  const keys = Object.keys(promisesObj);
@@ -18798,11 +18291,11 @@ function aborted(x, startIndex = 0) {
18798
18291
  }
18799
18292
  return false;
18800
18293
  }
18801
- function prefixIssues(path5, issues) {
18294
+ function prefixIssues(path4, issues) {
18802
18295
  return issues.map((iss) => {
18803
18296
  var _a;
18804
18297
  (_a = iss).path ?? (_a.path = []);
18805
- iss.path.unshift(path5);
18298
+ iss.path.unshift(path4);
18806
18299
  return iss;
18807
18300
  });
18808
18301
  }
@@ -21671,7 +21164,7 @@ function finalize(ctx, schema2) {
21671
21164
  result.$schema = "http://json-schema.org/draft-07/schema#";
21672
21165
  } else if (ctx.target === "draft-04") {
21673
21166
  result.$schema = "http://json-schema.org/draft-04/schema#";
21674
- } else if (ctx.target === "openapi-3.0") {}
21167
+ } else if (ctx.target === "openapi-3.0") {} else {}
21675
21168
  if (ctx.external?.uri) {
21676
21169
  const id = ctx.external.registry.get(schema2)?.id;
21677
21170
  if (!id)
@@ -21890,7 +21383,7 @@ var literalProcessor = (schema2, ctx, json2, _params) => {
21890
21383
  if (val === undefined) {
21891
21384
  if (ctx.unrepresentable === "throw") {
21892
21385
  throw new Error("Literal `undefined` cannot be represented in JSON Schema");
21893
- }
21386
+ } else {}
21894
21387
  } else if (typeof val === "bigint") {
21895
21388
  if (ctx.unrepresentable === "throw") {
21896
21389
  throw new Error("BigInt literals cannot be represented in JSON Schema");
@@ -25074,211 +24567,6 @@ class StdioServerTransport {
25074
24567
  // lib/mcp-server.ts
25075
24568
  var interpretEscapes = (str2) => (str2 || "").replace(/\\n/g, `
25076
24569
  `).replace(/\\t/g, "\t").replace(/\\r/g, "\r").replace(/\\"/g, '"').replace(/\\'/g, "'");
25077
- var JOE_TOOL_TO_COMMAND = {
25078
- plan_query: "plan",
25079
- explain_query: "explain",
25080
- exec_sql: "exec",
25081
- hypo_index: "hypo",
25082
- activity: "activity",
25083
- terminate_backend: "terminate",
25084
- reset_clone: "reset",
25085
- describe: "describe"
25086
- };
25087
- function joeToolDefinitions() {
25088
- const sessionProps = {
25089
- project_id: {
25090
- type: "string",
25091
- description: "Target project by numeric id OR alias/name (id-or-alias). --project 12 ≡ main-db."
25092
- },
25093
- session_id: {
25094
- type: "string",
25095
- description: "Run on a specific session's clone (64-bit id as a string). Auto-reused per project when omitted."
25096
- },
25097
- new_session: { type: "boolean", description: "Force a fresh session/clone (null session)." },
25098
- timeout_ms: { type: "number", description: "One-shot poll budget in ms (≤ 25000); on expiry returns a command_id to resume." },
25099
- debug: { type: "boolean", description: "Enable verbose debug logs." }
25100
- };
25101
- return [
25102
- {
25103
- name: "plan_query",
25104
- description: "Plan a query (EXPLAIN, plan-only — NEVER executes; the fast/safe default). Returns a sanitized plan tree (literals redacted). LLM-facing: feeds the plan/stats to your LLM when the org's AI features are enabled. Requires joe:plan.",
25105
- inputSchema: {
25106
- type: "object",
25107
- properties: { sql: { type: "string", description: "The query to plan (one explainable statement)." }, ...sessionProps },
25108
- required: ["sql", "project_id"],
25109
- additionalProperties: false
25110
- }
25111
- },
25112
- {
25113
- name: "explain_query",
25114
- description: "EXPLAIN ANALYZE a query — EXECUTES it on the hardened, ephemeral DBLab clone and returns the measured, annotated plan (node types, costs, actual-rows/timings, buffers — literals redacted), NOT raw SELECT result rows. LLM-facing: feeds the measured plan/stats to your LLM when AI features are enabled. Requires joe:exec; subject to the org execution policy (read_only allows SELECT-only).",
25115
- inputSchema: {
25116
- type: "object",
25117
- properties: { sql: { type: "string", description: "The query to EXPLAIN ANALYZE (executes on the clone)." }, ...sessionProps },
25118
- required: ["sql", "project_id"],
25119
- additionalProperties: false
25120
- }
25121
- },
25122
- {
25123
- name: "exec_sql",
25124
- description: "WARNING: executes arbitrary SQL (DDL/DML) on the disposable clone and CAN RETURN REAL (own-data) TABLE ROWS that will enter this agent's LLM context (subject to the agent row-cap / metadata-only minimization). Prefer plan_query (non-executing) when you only need a plan. Requires an explicit joe:exec grant and is subject to the org's joe_execution_policy.",
25125
- inputSchema: {
25126
- type: "object",
25127
- properties: { sql: { type: "string", description: "Arbitrary DDL/DML to run on the clone (e.g. create index, analyze)." }, ...sessionProps },
25128
- required: ["sql", "project_id"],
25129
- additionalProperties: false
25130
- }
25131
- },
25132
- {
25133
- name: "hypo_index",
25134
- description: "HypoPG hypothetical index + re-plan (no real index built, no data change) — would the planner switch to it? LLM-facing. Requires joe:plan.",
25135
- inputSchema: {
25136
- type: "object",
25137
- properties: {
25138
- sql: { type: "string", description: "Candidate index DDL (e.g. create index on orders (customer_id))." },
25139
- query: { type: "string", description: "Target query the hypothetical index is evaluated against." },
25140
- ...sessionProps
25141
- },
25142
- required: ["sql", "query", "project_id"],
25143
- additionalProperties: false
25144
- }
25145
- },
25146
- {
25147
- name: "activity",
25148
- description: "pg_stat_activity snapshot on the clone (query text redacted). LLM-facing. Requires joe:plan.",
25149
- inputSchema: { type: "object", properties: { ...sessionProps }, required: ["project_id"], additionalProperties: false }
25150
- },
25151
- {
25152
- name: "terminate_backend",
25153
- description: "pg_terminate_backend(pid) on the clone. Status-only (no data to the LLM). Requires joe:admin.",
25154
- inputSchema: {
25155
- type: "object",
25156
- properties: { pid: { type: "number", description: "Backend pid to terminate." }, ...sessionProps },
25157
- required: ["pid", "project_id"],
25158
- additionalProperties: false
25159
- }
25160
- },
25161
- {
25162
- name: "reset_clone",
25163
- description: "Reset/recreate the session's thin clone. Status-only (no data to the LLM). Requires joe:admin.",
25164
- inputSchema: { type: "object", properties: { ...sessionProps }, required: ["project_id"], additionalProperties: false }
25165
- },
25166
- {
25167
- name: "describe",
25168
- description: "\\d-family schema/relation/index/db/view metadata introspection. LLM-facing (metadata). Requires joe:plan.",
25169
- inputSchema: {
25170
- type: "object",
25171
- properties: {
25172
- object: { type: "string", description: "Object to describe (e.g. users)." },
25173
- variant: { type: "string", description: "\\d-family variant (e.g. \\d+, \\di, \\dt)." },
25174
- ...sessionProps
25175
- },
25176
- required: ["object", "project_id"],
25177
- additionalProperties: false
25178
- }
25179
- },
25180
- {
25181
- name: "list_projects",
25182
- description: "List the org's projects and which have Joe ready (org-level discovery — NOT a Joe endpoint; mirrors `pgai projects`). Use it to find the project_ids/aliases you may address with the Joe command tools.",
25183
- inputSchema: {
25184
- type: "object",
25185
- properties: {
25186
- org_id: { type: "number", description: "Organization ID (optional, falls back to config)." },
25187
- debug: { type: "boolean", description: "Enable verbose debug logs." }
25188
- },
25189
- additionalProperties: false
25190
- }
25191
- }
25192
- ];
25193
- }
25194
- function sanitizeBudgetMs(value) {
25195
- if (value === undefined || value === null) {
25196
- return;
25197
- }
25198
- const n = Number(value);
25199
- return Number.isFinite(n) ? n : undefined;
25200
- }
25201
- async function runJoeTool(params) {
25202
- const command = JOE_TOOL_TO_COMMAND[params.toolName];
25203
- const a = params.args;
25204
- const project = String(a.project_id ?? "").trim();
25205
- if (!project) {
25206
- return { content: [{ type: "text", text: "project_id is required" }], isError: true };
25207
- }
25208
- let sql = null;
25209
- let cmdArgs = null;
25210
- if (command === "plan" || command === "explain" || command === "exec" || command === "hypo") {
25211
- sql = String(a.sql ?? "").trim();
25212
- if (!sql) {
25213
- return { content: [{ type: "text", text: "sql is required" }], isError: true };
25214
- }
25215
- }
25216
- if (command === "hypo") {
25217
- const query = String(a.query ?? "").trim();
25218
- if (!query) {
25219
- return { content: [{ type: "text", text: "query is required for hypo_index" }], isError: true };
25220
- }
25221
- cmdArgs = { query };
25222
- }
25223
- if (command === "terminate") {
25224
- const raw = a.pid;
25225
- const pid = typeof raw === "number" ? raw : typeof raw === "string" && /^[0-9]+$/.test(raw.trim()) ? Number(raw.trim()) : Number.NaN;
25226
- if (!Number.isInteger(pid) || pid <= 0) {
25227
- return {
25228
- content: [{ type: "text", text: "pid must be a strict positive integer (backend pid) for terminate_backend" }],
25229
- isError: true
25230
- };
25231
- }
25232
- cmdArgs = { pid };
25233
- }
25234
- if (command === "describe") {
25235
- const object4 = String(a.object ?? "").trim();
25236
- if (!object4) {
25237
- return { content: [{ type: "text", text: "object is required for describe" }], isError: true };
25238
- }
25239
- cmdArgs = { object: object4 };
25240
- if (a.variant !== undefined)
25241
- cmdArgs.variant = String(a.variant);
25242
- }
25243
- const session = a.session_id !== undefined && a.session_id !== null ? String(a.session_id) : null;
25244
- const newSession = a.new_session === true;
25245
- const budgetMs = sanitizeBudgetMs(a.timeout_ms);
25246
- const outcome = await executeJoeCommand({
25247
- apiKey: params.apiKey,
25248
- apiBaseUrl: params.apiBaseUrl,
25249
- command,
25250
- project,
25251
- sql,
25252
- args: cmdArgs,
25253
- session,
25254
- newSession,
25255
- orgId: params.orgId,
25256
- budgetMs,
25257
- debug: params.debug
25258
- });
25259
- const payload = {
25260
- command,
25261
- command_id: outcome.commandId,
25262
- session_id: outcome.sessionId,
25263
- status: outcome.status,
25264
- budget_expired: outcome.budgetExpired,
25265
- resume: outcome.budgetExpired ? `pgai joe result ${outcome.commandId}` : undefined,
25266
- result: outcome.result
25267
- };
25268
- const isError = outcome.status === "error" || outcome.status === "timed_out";
25269
- return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }], isError };
25270
- }
25271
- async function resolveDblabInstanceForTool(apiKey, apiBaseUrl, args, cfgOrgId, debug) {
25272
- const project = String(args.project || "").trim();
25273
- if (!project) {
25274
- throw new Error("project is required (project id or alias)");
25275
- }
25276
- const orgId = args.org_id !== undefined ? Number(args.org_id) : cfgOrgId ?? undefined;
25277
- return resolveDblabInstanceId({ apiKey, apiBaseUrl, project, orgId, debug });
25278
- }
25279
- function optStr(v) {
25280
- return v !== undefined && v !== null && String(v).length > 0 ? String(v) : undefined;
25281
- }
25282
24570
  async function handleToolCall(req, rootOpts, extra) {
25283
24571
  const toolName = req.params.name;
25284
24572
  const args = req.params.arguments || {};
@@ -25556,147 +24844,6 @@ async function handleToolCall(req, rootOpts, extra) {
25556
24844
  const files = await fetchReportFileData({ apiKey, apiBaseUrl, reportId, type: type2, checkId, debug });
25557
24845
  return { content: [{ type: "text", text: JSON.stringify(files, null, 2) }] };
25558
24846
  }
25559
- if (toolName === "list_projects") {
25560
- const orgId = args.org_id !== undefined ? Number(args.org_id) : cfg.orgId ?? undefined;
25561
- const projects = await listProjects({ apiKey, apiBaseUrl, orgId, debug });
25562
- return { content: [{ type: "text", text: JSON.stringify(projects, null, 2) }] };
25563
- }
25564
- if (toolName in JOE_TOOL_TO_COMMAND) {
25565
- const orgId = cfg.orgId ?? undefined;
25566
- return await runJoeTool({ toolName, apiKey, apiBaseUrl, orgId, args, debug });
25567
- }
25568
- if (toolName === "clone_create") {
25569
- const instanceId = await resolveDblabInstanceForTool(apiKey, apiBaseUrl, args, cfg.orgId, debug);
25570
- const result = await createClone({
25571
- apiKey,
25572
- apiBaseUrl,
25573
- instanceId,
25574
- cloneId: optStr(args.clone_id),
25575
- branch: optStr(args.branch),
25576
- snapshotId: optStr(args.snapshot_id),
25577
- dbUser: optStr(args.db_user),
25578
- dbPassword: optStr(args.db_password),
25579
- isProtected: args.protected === true,
25580
- debug
25581
- });
25582
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
25583
- }
25584
- if (toolName === "clone_list") {
25585
- const instanceId = await resolveDblabInstanceForTool(apiKey, apiBaseUrl, args, cfg.orgId, debug);
25586
- const result = await listClones({ apiKey, apiBaseUrl, instanceId, debug });
25587
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
25588
- }
25589
- if (toolName === "clone_status") {
25590
- const cloneId = String(args.clone_id || "").trim();
25591
- if (!cloneId)
25592
- return { content: [{ type: "text", text: "clone_id is required" }], isError: true };
25593
- const instanceId = await resolveDblabInstanceForTool(apiKey, apiBaseUrl, args, cfg.orgId, debug);
25594
- const result = await getClone({ apiKey, apiBaseUrl, instanceId, cloneId, debug });
25595
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
25596
- }
25597
- if (toolName === "clone_reset") {
25598
- const cloneId = String(args.clone_id || "").trim();
25599
- if (!cloneId)
25600
- return { content: [{ type: "text", text: "clone_id is required" }], isError: true };
25601
- const instanceId = await resolveDblabInstanceForTool(apiKey, apiBaseUrl, args, cfg.orgId, debug);
25602
- const result = await resetClone({
25603
- apiKey,
25604
- apiBaseUrl,
25605
- instanceId,
25606
- cloneId,
25607
- snapshotId: optStr(args.snapshot_id),
25608
- latest: args.latest === true ? true : undefined,
25609
- debug
25610
- });
25611
- return { content: [{ type: "text", text: JSON.stringify(result ?? { reset: true, cloneId }, null, 2) }] };
25612
- }
25613
- if (toolName === "clone_destroy") {
25614
- const cloneId = String(args.clone_id || "").trim();
25615
- if (!cloneId)
25616
- return { content: [{ type: "text", text: "clone_id is required" }], isError: true };
25617
- const instanceId = await resolveDblabInstanceForTool(apiKey, apiBaseUrl, args, cfg.orgId, debug);
25618
- const result = await destroyClone({ apiKey, apiBaseUrl, instanceId, cloneId, debug });
25619
- return { content: [{ type: "text", text: JSON.stringify(result ?? { destroyed: true, cloneId }, null, 2) }] };
25620
- }
25621
- if (toolName === "branch_list") {
25622
- const instanceId = await resolveDblabInstanceForTool(apiKey, apiBaseUrl, args, cfg.orgId, debug);
25623
- const result = await listBranches({ apiKey, apiBaseUrl, instanceId, debug });
25624
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
25625
- }
25626
- if (toolName === "branch_create") {
25627
- const branchName = String(args.name || "").trim();
25628
- if (!branchName)
25629
- return { content: [{ type: "text", text: "name is required" }], isError: true };
25630
- const instanceId = await resolveDblabInstanceForTool(apiKey, apiBaseUrl, args, cfg.orgId, debug);
25631
- const result = await createBranch({
25632
- apiKey,
25633
- apiBaseUrl,
25634
- instanceId,
25635
- branchName,
25636
- baseBranch: optStr(args.base_branch),
25637
- snapshotId: optStr(args.snapshot_id),
25638
- debug
25639
- });
25640
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
25641
- }
25642
- if (toolName === "branch_delete") {
25643
- const branchName = String(args.name || "").trim();
25644
- if (!branchName)
25645
- return { content: [{ type: "text", text: "name is required" }], isError: true };
25646
- const instanceId = await resolveDblabInstanceForTool(apiKey, apiBaseUrl, args, cfg.orgId, debug);
25647
- const result = await deleteBranch({ apiKey, apiBaseUrl, instanceId, branchName, debug });
25648
- return { content: [{ type: "text", text: JSON.stringify(result ?? { deleted: true, branch: branchName }, null, 2) }] };
25649
- }
25650
- if (toolName === "branch_log") {
25651
- const branchName = String(args.name || "").trim();
25652
- if (!branchName)
25653
- return { content: [{ type: "text", text: "name is required" }], isError: true };
25654
- const instanceId = await resolveDblabInstanceForTool(apiKey, apiBaseUrl, args, cfg.orgId, debug);
25655
- const result = await branchLog({ apiKey, apiBaseUrl, instanceId, branchName, debug });
25656
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
25657
- }
25658
- if (toolName === "snapshot_list") {
25659
- const instanceId = await resolveDblabInstanceForTool(apiKey, apiBaseUrl, args, cfg.orgId, debug);
25660
- const result = await listSnapshots({
25661
- apiKey,
25662
- apiBaseUrl,
25663
- instanceId,
25664
- branchName: optStr(args.branch),
25665
- dataset: optStr(args.dataset),
25666
- debug
25667
- });
25668
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
25669
- }
25670
- if (toolName === "snapshot_create") {
25671
- const cloneId = String(args.clone_id || "").trim();
25672
- if (!cloneId)
25673
- return { content: [{ type: "text", text: "clone_id is required" }], isError: true };
25674
- const instanceId = await resolveDblabInstanceForTool(apiKey, apiBaseUrl, args, cfg.orgId, debug);
25675
- const result = await createSnapshot({
25676
- apiKey,
25677
- apiBaseUrl,
25678
- instanceId,
25679
- cloneId,
25680
- message: optStr(args.message),
25681
- debug
25682
- });
25683
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
25684
- }
25685
- if (toolName === "snapshot_destroy") {
25686
- const snapshotId = String(args.snapshot_id || "").trim();
25687
- if (!snapshotId)
25688
- return { content: [{ type: "text", text: "snapshot_id is required" }], isError: true };
25689
- const instanceId = await resolveDblabInstanceForTool(apiKey, apiBaseUrl, args, cfg.orgId, debug);
25690
- const result = await destroySnapshot({
25691
- apiKey,
25692
- apiBaseUrl,
25693
- instanceId,
25694
- snapshotId,
25695
- force: args.force === true,
25696
- debug
25697
- });
25698
- return { content: [{ type: "text", text: JSON.stringify(result ?? { destroyed: true, snapshot: snapshotId }, null, 2) }] };
25699
- }
25700
24847
  throw new Error(`Unknown tool: ${toolName}`);
25701
24848
  } catch (err) {
25702
24849
  const message = err instanceof Error ? err.message : String(err);
@@ -25973,197 +25120,6 @@ async function startMcpServer(rootOpts, extra) {
25973
25120
  },
25974
25121
  additionalProperties: false
25975
25122
  }
25976
- },
25977
- ...joeToolDefinitions(),
25978
- {
25979
- name: "clone_create",
25980
- description: "Create a DBLab thin clone of the project's database (same as CLI 'pgai dblab clone create'). Requires the joe:plan scope.",
25981
- inputSchema: {
25982
- type: "object",
25983
- properties: {
25984
- project: { type: "string", description: "Project id or alias" },
25985
- branch: { type: "string", description: "Branch to clone from" },
25986
- snapshot_id: { type: "string", description: "Snapshot id to clone from" },
25987
- clone_id: { type: "string", description: "Clone id (DBLab generates one when omitted)" },
25988
- db_user: { type: "string", description: "Clone DB user" },
25989
- db_password: { type: "string", description: "Clone DB password" },
25990
- protected: { type: "boolean", description: "Protect the clone from auto-deletion" },
25991
- org_id: { type: "number", description: "Organization id (optional, falls back to config)" },
25992
- debug: { type: "boolean", description: "Enable verbose debug logs" }
25993
- },
25994
- required: ["project"],
25995
- additionalProperties: false
25996
- }
25997
- },
25998
- {
25999
- name: "clone_list",
26000
- description: "List the project's DBLab thin clones (same as CLI 'pgai dblab clone list').",
26001
- inputSchema: {
26002
- type: "object",
26003
- properties: {
26004
- project: { type: "string", description: "Project id or alias" },
26005
- org_id: { type: "number", description: "Organization id (optional, falls back to config)" },
26006
- debug: { type: "boolean", description: "Enable verbose debug logs" }
26007
- },
26008
- required: ["project"],
26009
- additionalProperties: false
26010
- }
26011
- },
26012
- {
26013
- name: "clone_status",
26014
- description: "Show a DBLab clone's status (same as CLI 'pgai dblab clone status').",
26015
- inputSchema: {
26016
- type: "object",
26017
- properties: {
26018
- project: { type: "string", description: "Project id or alias" },
26019
- clone_id: { type: "string", description: "Clone id" },
26020
- org_id: { type: "number", description: "Organization id (optional, falls back to config)" },
26021
- debug: { type: "boolean", description: "Enable verbose debug logs" }
26022
- },
26023
- required: ["project", "clone_id"],
26024
- additionalProperties: false
26025
- }
26026
- },
26027
- {
26028
- name: "clone_reset",
26029
- description: "Reset a DBLab clone to a pristine snapshot (same as CLI 'pgai dblab clone reset'). Requires the joe:admin scope.",
26030
- inputSchema: {
26031
- type: "object",
26032
- properties: {
26033
- project: { type: "string", description: "Project id or alias" },
26034
- clone_id: { type: "string", description: "Clone id" },
26035
- snapshot_id: { type: "string", description: "Snapshot id to reset to (default: latest)" },
26036
- latest: { type: "boolean", description: "Reset to the latest snapshot" },
26037
- org_id: { type: "number", description: "Organization id (optional, falls back to config)" },
26038
- debug: { type: "boolean", description: "Enable verbose debug logs" }
26039
- },
26040
- required: ["project", "clone_id"],
26041
- additionalProperties: false
26042
- }
26043
- },
26044
- {
26045
- name: "clone_destroy",
26046
- description: "Destroy a DBLab clone (same as CLI 'pgai dblab clone destroy'). Requires the joe:admin scope.",
26047
- inputSchema: {
26048
- type: "object",
26049
- properties: {
26050
- project: { type: "string", description: "Project id or alias" },
26051
- clone_id: { type: "string", description: "Clone id" },
26052
- org_id: { type: "number", description: "Organization id (optional, falls back to config)" },
26053
- debug: { type: "boolean", description: "Enable verbose debug logs" }
26054
- },
26055
- required: ["project", "clone_id"],
26056
- additionalProperties: false
26057
- }
26058
- },
26059
- {
26060
- name: "branch_list",
26061
- description: "List the project's DBLab branches (same as CLI 'pgai dblab branch list').",
26062
- inputSchema: {
26063
- type: "object",
26064
- properties: {
26065
- project: { type: "string", description: "Project id or alias" },
26066
- org_id: { type: "number", description: "Organization id (optional, falls back to config)" },
26067
- debug: { type: "boolean", description: "Enable verbose debug logs" }
26068
- },
26069
- required: ["project"],
26070
- additionalProperties: false
26071
- }
26072
- },
26073
- {
26074
- name: "branch_create",
26075
- description: "Create a DBLab branch (same as CLI 'pgai dblab branch create'). Requires the joe:plan scope.",
26076
- inputSchema: {
26077
- type: "object",
26078
- properties: {
26079
- project: { type: "string", description: "Project id or alias" },
26080
- name: { type: "string", description: "Branch name" },
26081
- snapshot_id: { type: "string", description: "Snapshot id to base the branch on" },
26082
- base_branch: { type: "string", description: "Parent branch to fork from" },
26083
- org_id: { type: "number", description: "Organization id (optional, falls back to config)" },
26084
- debug: { type: "boolean", description: "Enable verbose debug logs" }
26085
- },
26086
- required: ["project", "name"],
26087
- additionalProperties: false
26088
- }
26089
- },
26090
- {
26091
- name: "branch_delete",
26092
- description: "Delete a DBLab branch (same as CLI 'pgai dblab branch delete'). Requires the joe:admin scope.",
26093
- inputSchema: {
26094
- type: "object",
26095
- properties: {
26096
- project: { type: "string", description: "Project id or alias" },
26097
- name: { type: "string", description: "Branch name" },
26098
- org_id: { type: "number", description: "Organization id (optional, falls back to config)" },
26099
- debug: { type: "boolean", description: "Enable verbose debug logs" }
26100
- },
26101
- required: ["project", "name"],
26102
- additionalProperties: false
26103
- }
26104
- },
26105
- {
26106
- name: "branch_log",
26107
- description: "Show a DBLab branch's snapshot log (same as CLI 'pgai dblab branch log').",
26108
- inputSchema: {
26109
- type: "object",
26110
- properties: {
26111
- project: { type: "string", description: "Project id or alias" },
26112
- name: { type: "string", description: "Branch name" },
26113
- org_id: { type: "number", description: "Organization id (optional, falls back to config)" },
26114
- debug: { type: "boolean", description: "Enable verbose debug logs" }
26115
- },
26116
- required: ["project", "name"],
26117
- additionalProperties: false
26118
- }
26119
- },
26120
- {
26121
- name: "snapshot_list",
26122
- description: "List the project's DBLab snapshots (same as CLI 'pgai dblab snapshot list').",
26123
- inputSchema: {
26124
- type: "object",
26125
- properties: {
26126
- project: { type: "string", description: "Project id or alias" },
26127
- branch: { type: "string", description: "Filter by branch" },
26128
- dataset: { type: "string", description: "Filter by dataset" },
26129
- org_id: { type: "number", description: "Organization id (optional, falls back to config)" },
26130
- debug: { type: "boolean", description: "Enable verbose debug logs" }
26131
- },
26132
- required: ["project"],
26133
- additionalProperties: false
26134
- }
26135
- },
26136
- {
26137
- name: "snapshot_create",
26138
- description: "Create a DBLab snapshot from a clone (same as CLI 'pgai dblab snapshot create'). Requires the joe:plan scope.",
26139
- inputSchema: {
26140
- type: "object",
26141
- properties: {
26142
- project: { type: "string", description: "Project id or alias" },
26143
- clone_id: { type: "string", description: "Clone id to snapshot" },
26144
- message: { type: "string", description: "Snapshot message" },
26145
- org_id: { type: "number", description: "Organization id (optional, falls back to config)" },
26146
- debug: { type: "boolean", description: "Enable verbose debug logs" }
26147
- },
26148
- required: ["project", "clone_id"],
26149
- additionalProperties: false
26150
- }
26151
- },
26152
- {
26153
- name: "snapshot_destroy",
26154
- description: "Destroy a DBLab snapshot (same as CLI 'pgai dblab snapshot destroy'). Requires the joe:admin scope.",
26155
- inputSchema: {
26156
- type: "object",
26157
- properties: {
26158
- project: { type: "string", description: "Project id or alias" },
26159
- snapshot_id: { type: "string", description: "Snapshot id" },
26160
- force: { type: "boolean", description: "Force-delete even when dependent clones exist" },
26161
- org_id: { type: "number", description: "Organization id (optional, falls back to config)" },
26162
- debug: { type: "boolean", description: "Enable verbose debug logs" }
26163
- },
26164
- required: ["project", "snapshot_id"],
26165
- additionalProperties: false
26166
- }
26167
25123
  }
26168
25124
  ]
26169
25125
  };
@@ -27037,39 +25993,9 @@ function renderMarkdownForTerminal(md) {
27037
25993
  }
27038
25994
 
27039
25995
  // lib/joe.ts
27040
- import * as fs5 from "fs";
27041
- import * as path5 from "path";
27042
- import * as crypto2 from "node:crypto";
27043
- var JOE_COMMANDS2 = [
27044
- "plan",
27045
- "explain",
27046
- "exec",
27047
- "hypo",
27048
- "activity",
27049
- "terminate",
27050
- "reset",
27051
- "describe"
27052
- ];
27053
- var TERMINAL_STATES2 = new Set([
27054
- "done",
27055
- "error",
27056
- "timed_out"
27057
- ]);
27058
- var AI_ENABLED_COMMANDS2 = new Set([
27059
- "plan",
27060
- "explain",
27061
- "exec",
27062
- "hypo",
27063
- "activity",
27064
- "describe"
27065
- ]);
27066
- var EXECUTING_COMMANDS2 = new Set([
27067
- "exec",
27068
- "explain"
27069
- ]);
27070
- var DEFAULT_BUDGET_MS2 = 25000;
27071
- var DEFAULT_POLL_INTERVAL_MS2 = 800;
27072
- async function callRpc2(params) {
25996
+ var DEFAULT_BUDGET_MS = 25000;
25997
+ var DEFAULT_POLL_INTERVAL_MS = 800;
25998
+ async function callRpc(params) {
27073
25999
  const { apiKey, apiBaseUrl, fn, body, operation, debug } = params;
27074
26000
  if (!apiKey) {
27075
26001
  throw new Error("API key is required");
@@ -27089,13 +26015,18 @@ async function callRpc2(params) {
27089
26015
  console.error(`Debug: Request body: ${redactSecretsForLog(payload)}`);
27090
26016
  }
27091
26017
  let response;
26018
+ const requestTimeout = requestTimeoutSignal(params.timeoutMs);
27092
26019
  try {
27093
26020
  response = await fetch(url.toString(), {
27094
26021
  method: "POST",
27095
26022
  headers,
27096
- body: payload
26023
+ body: payload,
26024
+ signal: requestTimeout.signal
27097
26025
  });
27098
26026
  } catch (err) {
26027
+ if (isFetchTimeout(err)) {
26028
+ throw new HttpRequestTimeoutError(operation, requestTimeout.timeoutMs);
26029
+ }
27099
26030
  throw new Error(describeFetchError(operation, base, err));
27100
26031
  }
27101
26032
  const text = await response.text();
@@ -27104,86 +26035,72 @@ async function callRpc2(params) {
27104
26035
  console.error(`Debug: Response body: ${redactSecretsForLog(text)}`);
27105
26036
  }
27106
26037
  if (!response.ok) {
27107
- throw new Error(formatHttpError(operation, response.status, text, response.statusText));
26038
+ throw new HttpStatusError(formatHttpError(operation, response.status, text, response.statusText), response.status);
27108
26039
  }
27109
26040
  try {
27110
26041
  return JSON.parse(text);
27111
26042
  } catch {
27112
- throw new Error(`${operation}: failed to parse response: ${text}`);
26043
+ throw new Error(`${operation}: failed to parse response: ${redactSecretsForLog(text)}`);
27113
26044
  }
27114
26045
  }
27115
- async function submitCommand2(params) {
27116
- const { apiKey, apiBaseUrl, command, projectId, sql, args, sessionId, idempotencyKey, debug } = params;
27117
- if (!JOE_COMMANDS2.includes(command)) {
27118
- throw new Error(`Unknown Joe command: ${command}`);
27119
- }
27120
- const body = {
27121
- project_id: projectId,
27122
- command,
27123
- sql: sql ?? null,
27124
- args: args ?? null,
27125
- session_id: sessionId ?? null
27126
- };
27127
- if (idempotencyKey) {
27128
- body.idempotency_key = idempotencyKey;
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");
27129
26050
  }
27130
- return callRpc2({
26051
+ const commandId = await callRpc({
27131
26052
  apiKey,
27132
26053
  apiBaseUrl,
27133
- fn: "joe_command_submit",
27134
- body,
27135
- operation: `Failed to submit ${command} command`,
27136
- debug
26054
+ fn: "joe_command_run",
26055
+ body: { instance_id: instanceId, command },
26056
+ operation: "Failed to run Joe command",
26057
+ debug,
26058
+ timeoutMs
27137
26059
  });
27138
- }
27139
- async function getCommandStatus2(params) {
27140
- const { apiKey, apiBaseUrl, commandId, debug } = params;
27141
- if (!commandId) {
27142
- throw new Error("commandId is required");
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))}`);
27143
26062
  }
27144
- return callRpc2({
27145
- apiKey,
27146
- apiBaseUrl,
27147
- fn: "joe_command_status",
27148
- body: { command_id: commandId },
27149
- operation: "Failed to fetch command status",
27150
- debug
27151
- });
26063
+ return commandId;
27152
26064
  }
27153
- async function getCommandResult2(params) {
27154
- const { apiKey, apiBaseUrl, commandId, debug } = params;
26065
+ async function getCommandOutput(params) {
26066
+ const { apiKey, apiBaseUrl, commandId, debug, timeoutMs } = params;
27155
26067
  if (!commandId) {
27156
26068
  throw new Error("commandId is required");
27157
26069
  }
27158
- return callRpc2({
26070
+ return callRpc({
27159
26071
  apiKey,
27160
26072
  apiBaseUrl,
27161
- fn: "joe_command_result",
26073
+ fn: "joe_command_output",
27162
26074
  body: { command_id: commandId },
27163
- operation: "Failed to fetch command result",
27164
- debug
26075
+ operation: "Failed to fetch command output",
26076
+ debug,
26077
+ timeoutMs
27165
26078
  });
27166
26079
  }
27167
- function normalizeProjectRow2(row) {
27168
- const projectId = row.project_id ?? row.id ?? 0;
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) {
27169
26087
  return {
27170
- project_id: Number(projectId),
26088
+ project_id: preserveIntegerId(row.project_id ?? 0),
27171
26089
  alias: row.alias ?? null,
27172
26090
  name: row.name ?? null,
27173
- label: row.label ?? null,
27174
- joe_ready: Boolean(row.joe_ready ?? row.joe_api_v2_enabled ?? false),
27175
- tunnel: Boolean(row.tunnel ?? row.tunnel_ready ?? false),
27176
- instance_id: row.instance_id == null ? null : Number(row.instance_id),
27177
- dblab_instance_id: row.dblab_instance_id == null ? null : Number(row.dblab_instance_id)
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)
27178
26095
  };
27179
26096
  }
27180
- async function listProjects2(params) {
26097
+ async function listProjects(params) {
27181
26098
  const { apiKey, apiBaseUrl, orgId, debug } = params;
27182
26099
  const body = {};
27183
26100
  if (typeof orgId === "number") {
27184
26101
  body.org_id = orgId;
27185
26102
  }
27186
- const rows = await callRpc2({
26103
+ const rows = await callRpc({
27187
26104
  apiKey,
27188
26105
  apiBaseUrl,
27189
26106
  fn: "projects_list",
@@ -27194,166 +26111,158 @@ async function listProjects2(params) {
27194
26111
  if (!Array.isArray(rows)) {
27195
26112
  return [];
27196
26113
  }
27197
- return rows.map(normalizeProjectRow2);
26114
+ return rows.map(normalizeProjectRow);
27198
26115
  }
27199
- function isNumericProjectRef3(ref) {
26116
+ function isNumericProjectRef(ref) {
27200
26117
  return /^[0-9]+$/.test(ref.trim());
27201
26118
  }
27202
- async function resolveProjectId2(params) {
26119
+ async function resolveJoeInstanceId(params) {
27203
26120
  const ref = String(params.project ?? "").trim();
27204
26121
  if (!ref) {
27205
26122
  throw new Error("project is required (--project <id|alias>)");
27206
26123
  }
27207
- if (isNumericProjectRef3(ref)) {
27208
- return Number(ref);
27209
- }
27210
- const projects = await listProjects2({
26124
+ const projects = await listProjects({
27211
26125
  apiKey: params.apiKey,
27212
26126
  apiBaseUrl: params.apiBaseUrl,
27213
26127
  orgId: params.orgId,
27214
26128
  debug: params.debug
27215
26129
  });
27216
26130
  const needle = ref.toLowerCase();
27217
- const match = projects.find((p) => p.alias !== null && p.alias.toLowerCase() === needle || p.name !== null && p.name.toLowerCase() === needle || p.label !== null && p.label.toLowerCase() === needle);
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);
27218
26132
  if (!match) {
27219
- throw new Error(`Project not found for alias/name '${ref}'. Run 'pgai projects' to see available projects.`);
27220
- }
27221
- return match.project_id;
27222
- }
27223
- function sessionStorePath2(dir) {
27224
- return path5.join(dir ?? getConfigDir2(), "joe-sessions.json");
27225
- }
27226
- function readSessionStore2(dir) {
27227
- const file = sessionStorePath2(dir);
27228
- if (!fs5.existsSync(file)) {
27229
- return {};
27230
- }
27231
- try {
27232
- const parsed = JSON.parse(fs5.readFileSync(file, "utf8"));
27233
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
27234
- return parsed;
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}`;
27235
26184
  }
27236
- } catch {}
27237
- return {};
27238
- }
27239
- function readStoredSessionId2(projectId, dir) {
27240
- const store = readSessionStore2(dir);
27241
- const value = store[String(projectId)];
27242
- return typeof value === "string" && value.length > 0 ? value : null;
27243
- }
27244
- function writeStoredSessionId2(projectId, sessionId, dir) {
27245
- const baseDir = dir ?? getConfigDir2();
27246
- if (!fs5.existsSync(baseDir)) {
27247
- fs5.mkdirSync(baseDir, { recursive: true, mode: 448 });
27248
- }
27249
- const store = readSessionStore2(dir);
27250
- store[String(projectId)] = sessionId;
27251
- fs5.writeFileSync(sessionStorePath2(dir), JSON.stringify(store, null, 2) + `
27252
- `, { mode: 384 });
27253
- }
27254
- function clearStoredSessionId2(projectId, dir) {
27255
- const file = sessionStorePath2(dir);
27256
- if (!fs5.existsSync(file)) {
27257
- return;
27258
- }
27259
- const store = readSessionStore2(dir);
27260
- if (String(projectId) in store) {
27261
- delete store[String(projectId)];
27262
- fs5.writeFileSync(file, JSON.stringify(store, null, 2) + `
27263
- `, { mode: 384 });
27264
- }
27265
- }
27266
- function computeIdempotencyKey2(command, projectId, sql, args) {
27267
- if (EXECUTING_COMMANDS2.has(command)) {
27268
- const canonical = `${projectId}
27269
- ${command}
27270
- ${sql ?? ""}
27271
- ${JSON.stringify(args ?? null)}`;
27272
- return `joe-${crypto2.createHash("sha256").update(canonical).digest("hex")}`;
27273
26185
  }
27274
- return `joe-${crypto2.randomUUID()}`;
27275
26186
  }
27276
- var defaultSleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
27277
- async function runCommand2(params) {
27278
- const {
27279
- apiKey,
27280
- apiBaseUrl,
27281
- command,
27282
- projectId,
27283
- sql,
27284
- args,
27285
- sessionId,
27286
- debug
27287
- } = params;
27288
- const budgetMs = typeof params.budgetMs === "number" && Number.isFinite(params.budgetMs) ? params.budgetMs : DEFAULT_BUDGET_MS2;
27289
- const pollIntervalMs = params.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS2;
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;
27290
26192
  const now = params.now ?? Date.now;
27291
- const sleep = params.sleep ?? defaultSleep2;
27292
- const idempotencyKey = params.idempotencyKey ?? computeIdempotencyKey2(command, projectId, sql, args);
27293
- const submitted = await submitCommand2({
27294
- apiKey,
27295
- apiBaseUrl,
27296
- command,
27297
- projectId,
27298
- sql,
27299
- args,
27300
- sessionId,
27301
- idempotencyKey,
27302
- debug
27303
- });
27304
- const commandId = submitted.command_id;
27305
- const newSessionId = submitted.session_id ?? sessionId ?? null;
26193
+ const sleep = params.sleep ?? defaultSleep;
26194
+ const commandId = await startCommand({ apiKey, apiBaseUrl, instanceId, command, debug });
27306
26195
  const deadline = now() + budgetMs;
27307
- let status = submitted.status;
27308
- while (true) {
27309
- const statusResp = await getCommandStatus2({ apiKey, apiBaseUrl, commandId, debug });
27310
- status = statusResp.status;
27311
- if (TERMINAL_STATES2.has(status)) {
27312
- break;
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 };
27313
26227
  }
27314
26228
  if (now() >= deadline) {
27315
- return { commandId, sessionId: newSessionId, status, result: null, budgetExpired: true };
26229
+ return { commandId, status, output: null, budgetExpired: true };
27316
26230
  }
27317
26231
  await sleep(pollIntervalMs);
27318
26232
  }
27319
- const result = await getCommandResult2({ apiKey, apiBaseUrl, commandId, debug });
27320
- return { commandId, sessionId: newSessionId, status, result, budgetExpired: false };
27321
26233
  }
27322
- async function executeJoeCommand2(params) {
27323
- const projectId = await resolveProjectId2({
27324
- apiKey: params.apiKey,
27325
- apiBaseUrl: params.apiBaseUrl,
27326
- project: params.project,
27327
- orgId: params.orgId,
27328
- debug: params.debug
27329
- });
27330
- let sessionId;
27331
- if (params.newSession) {
27332
- clearStoredSessionId2(projectId, params.sessionDir);
27333
- sessionId = null;
27334
- } else if (params.session) {
27335
- sessionId = String(params.session);
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
+ });
27336
26251
  } else {
27337
- sessionId = readStoredSessionId2(projectId, params.sessionDir);
26252
+ throw new Error("either instanceId or project is required");
27338
26253
  }
27339
- const outcome = await runCommand2({
26254
+ const outcome = await runCommand({
27340
26255
  apiKey: params.apiKey,
27341
26256
  apiBaseUrl: params.apiBaseUrl,
27342
- command: params.command,
27343
- projectId,
27344
- sql: params.sql,
27345
- args: params.args,
27346
- sessionId,
26257
+ instanceId,
26258
+ command: commandText,
27347
26259
  budgetMs: params.budgetMs,
27348
26260
  pollIntervalMs: params.pollIntervalMs,
27349
26261
  debug: params.debug,
27350
26262
  now: params.now,
27351
26263
  sleep: params.sleep
27352
26264
  });
27353
- if (outcome.sessionId) {
27354
- writeStoredSessionId2(projectId, outcome.sessionId, params.sessionDir);
27355
- }
27356
- return { ...outcome, command: params.command, projectId };
26265
+ return { ...outcome, command: params.command, instanceId, commandText };
27357
26266
  }
27358
26267
  function clientSidePlanFlags(planJson) {
27359
26268
  const flags = [];
@@ -27372,57 +26281,44 @@ function clientSidePlanFlags(planJson) {
27372
26281
  }
27373
26282
  }
27374
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
+ }
27375
26292
  if (planJson && typeof planJson === "object") {
27376
26293
  const root = planJson;
27377
26294
  walk(root.Plan ?? planJson);
27378
26295
  }
27379
26296
  return flags;
27380
26297
  }
27381
- function formatJoeResult(command, result) {
26298
+ function formatJoeOutput(output) {
27382
26299
  const lines = [];
27383
- switch (command) {
27384
- case "plan":
27385
- case "explain": {
27386
- if (result.plan_text) {
27387
- lines.push(result.plan_text);
27388
- }
27389
- for (const flag of clientSidePlanFlags(result.plan_json)) {
27390
- lines.push(`⚑ ${flag}`);
27391
- }
27392
- if (result.queryid || result.plan_fingerprint) {
27393
- lines.push(`(queryid ${result.queryid ?? "?"} · plan_fp ${result.plan_fingerprint ?? "?"})`);
27394
- }
27395
- break;
27396
- }
27397
- case "exec": {
27398
- const notices = result.notices ?? [];
27399
- const rowCount = result.row_count ?? 0;
27400
- lines.push(`${notices.join(" ") || "OK"} · ${rowCount} row${rowCount === 1 ? "" : "s"}`);
27401
- if (result.result_rows && result.result_rows.length > 0) {
27402
- lines.push(JSON.stringify(result.result_rows, null, 2));
27403
- }
27404
- break;
27405
- }
27406
- case "hypo": {
27407
- lines.push(result.hypo_used ? "hypothetical index would be used" : "hypothetical index would NOT be used");
27408
- if (result.hypo_plan !== undefined) {
27409
- lines.push(JSON.stringify(result.hypo_plan, null, 2));
27410
- }
27411
- break;
27412
- }
27413
- case "activity":
27414
- case "describe": {
27415
- lines.push(JSON.stringify(result.snapshot ?? null, null, 2));
27416
- break;
27417
- }
27418
- case "terminate": {
27419
- lines.push(`terminated ${result.terminated ? "yes" : "no"} · pid ${result.pid ?? "?"}`);
27420
- break;
27421
- }
27422
- case "reset": {
27423
- lines.push(`reset ${result.reset ? "ok" : "no"}`);
27424
- break;
27425
- }
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})`);
27426
26322
  }
27427
26323
  return lines.join(`
27428
26324
  `);
@@ -27442,293 +26338,11 @@ function formatProjectsTable(projects) {
27442
26338
  `);
27443
26339
  }
27444
26340
 
27445
- // lib/dblab.ts
27446
- function isNumericProjectRef4(ref) {
27447
- return /^[0-9]+$/.test(ref.trim());
27448
- }
27449
- async function resolveDblabInstanceId2(params) {
27450
- const { apiKey, apiBaseUrl, orgId, debug } = params;
27451
- if (!apiKey) {
27452
- throw new Error("API key is required");
27453
- }
27454
- const ref = String(params.project ?? "").trim();
27455
- if (!ref) {
27456
- throw new Error("project is required (--project <id|alias>)");
27457
- }
27458
- let projects;
27459
- try {
27460
- projects = await listProjects({ apiKey, apiBaseUrl, orgId, debug });
27461
- } catch (err) {
27462
- const message = err instanceof Error ? err.message : String(err);
27463
- throw new Error(`Failed to resolve project's DBLab instance: ${message}`);
27464
- }
27465
- const numeric = isNumericProjectRef4(ref);
27466
- const needle = ref.toLowerCase();
27467
- const match = projects.find((p) => {
27468
- if (numeric) {
27469
- return String(p.project_id) === ref;
27470
- }
27471
- return p.alias !== null && p.alias.toLowerCase() === needle || p.name !== null && p.name.toLowerCase() === needle || p.label !== null && p.label.toLowerCase() === needle;
27472
- });
27473
- if (!match) {
27474
- throw new Error(`No DBLab instance found for project '${ref}'. Run 'pgai projects' to see available projects.`);
27475
- }
27476
- if (match.dblab_instance_id == null) {
27477
- throw new Error(`Project '${ref}' has no active DBLab instance. Register a DBLab instance for it in the Console first.`);
27478
- }
27479
- return String(match.dblab_instance_id);
27480
- }
27481
- async function callDblabApi2(params) {
27482
- const { apiKey, apiBaseUrl, instanceId, action, method, data, operation, debug } = params;
27483
- if (!apiKey) {
27484
- throw new Error("API key is required");
27485
- }
27486
- if (!instanceId) {
27487
- throw new Error("instanceId is required");
27488
- }
27489
- const base = normalizeBaseUrl(apiBaseUrl);
27490
- const url = new URL(`${base}/rpc/dblab_api_call`);
27491
- const bodyObj = {
27492
- instance_id: instanceId,
27493
- action,
27494
- method
27495
- };
27496
- if (data !== undefined) {
27497
- bodyObj.data = data;
27498
- }
27499
- const body = JSON.stringify(bodyObj);
27500
- const headers = {
27501
- "access-token": apiKey,
27502
- "Content-Type": "application/json",
27503
- Connection: "close"
27504
- };
27505
- if (debug) {
27506
- const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
27507
- console.error(`Debug: POST URL: ${url.toString()}`);
27508
- console.error(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
27509
- console.error(`Debug: Request body: ${redactSecretsForLog(body)}`);
27510
- }
27511
- let response;
27512
- try {
27513
- response = await fetch(url.toString(), { method: "POST", headers, body });
27514
- } catch (err) {
27515
- throw new Error(describeFetchError(operation, base, err));
27516
- }
27517
- const text = await response.text();
27518
- if (debug) {
27519
- console.error(`Debug: Response status: ${response.status}`);
27520
- console.error(`Debug: Response body: ${redactSecretsForLog(text)}`);
27521
- }
27522
- if (!response.ok) {
27523
- throw new Error(formatHttpError(operation, response.status, text, response.statusText));
27524
- }
27525
- if (text.trim() === "") {
27526
- return null;
27527
- }
27528
- try {
27529
- return JSON.parse(text);
27530
- } catch {
27531
- throw new Error(`${operation}: failed to parse response: ${text}`);
27532
- }
27533
- }
27534
- async function createClone2(params) {
27535
- const { apiKey, apiBaseUrl, instanceId, cloneId, branch, snapshotId, dbUser, dbPassword, isProtected, debug } = params;
27536
- const data = { protected: Boolean(isProtected) };
27537
- if (cloneId)
27538
- data.id = cloneId;
27539
- if (branch)
27540
- data.branch = branch;
27541
- if (snapshotId)
27542
- data.snapshot = { id: snapshotId };
27543
- if (dbUser && dbPassword)
27544
- data.db = { username: dbUser, password: dbPassword };
27545
- return callDblabApi2({
27546
- apiKey,
27547
- apiBaseUrl,
27548
- instanceId,
27549
- action: "/clone",
27550
- method: "post",
27551
- data,
27552
- operation: "Failed to create clone",
27553
- debug
27554
- });
27555
- }
27556
- async function listClones2(params) {
27557
- const { apiKey, apiBaseUrl, instanceId, debug } = params;
27558
- return callDblabApi2({
27559
- apiKey,
27560
- apiBaseUrl,
27561
- instanceId,
27562
- action: "/clones",
27563
- method: "get",
27564
- operation: "Failed to list clones",
27565
- debug
27566
- });
27567
- }
27568
- async function getClone2(params) {
27569
- const { apiKey, apiBaseUrl, instanceId, cloneId, debug } = params;
27570
- if (!cloneId)
27571
- throw new Error("cloneId is required");
27572
- return callDblabApi2({
27573
- apiKey,
27574
- apiBaseUrl,
27575
- instanceId,
27576
- action: `/clone/${encodeURIComponent(cloneId)}`,
27577
- method: "get",
27578
- operation: "Failed to get clone",
27579
- debug
27580
- });
27581
- }
27582
- async function resetClone2(params) {
27583
- const { apiKey, apiBaseUrl, instanceId, cloneId, snapshotId, latest, debug } = params;
27584
- if (!cloneId)
27585
- throw new Error("cloneId is required");
27586
- const data = { latest: latest ?? !snapshotId };
27587
- if (snapshotId)
27588
- data.snapshotID = snapshotId;
27589
- return callDblabApi2({
27590
- apiKey,
27591
- apiBaseUrl,
27592
- instanceId,
27593
- action: `/clone/${encodeURIComponent(cloneId)}/reset`,
27594
- method: "post",
27595
- data,
27596
- operation: "Failed to reset clone",
27597
- debug
27598
- });
27599
- }
27600
- async function destroyClone2(params) {
27601
- const { apiKey, apiBaseUrl, instanceId, cloneId, debug } = params;
27602
- if (!cloneId)
27603
- throw new Error("cloneId is required");
27604
- return callDblabApi2({
27605
- apiKey,
27606
- apiBaseUrl,
27607
- instanceId,
27608
- action: `/clone/${encodeURIComponent(cloneId)}`,
27609
- method: "delete",
27610
- operation: "Failed to destroy clone",
27611
- debug
27612
- });
27613
- }
27614
- async function listBranches2(params) {
27615
- const { apiKey, apiBaseUrl, instanceId, debug } = params;
27616
- return callDblabApi2({
27617
- apiKey,
27618
- apiBaseUrl,
27619
- instanceId,
27620
- action: "/branches",
27621
- method: "get",
27622
- operation: "Failed to list branches",
27623
- debug
27624
- });
27625
- }
27626
- async function createBranch2(params) {
27627
- const { apiKey, apiBaseUrl, instanceId, branchName, baseBranch, snapshotId, debug } = params;
27628
- if (!branchName)
27629
- throw new Error("branchName is required");
27630
- const data = { branchName };
27631
- if (baseBranch)
27632
- data.baseBranch = baseBranch;
27633
- if (snapshotId)
27634
- data.snapshotID = snapshotId;
27635
- return callDblabApi2({
27636
- apiKey,
27637
- apiBaseUrl,
27638
- instanceId,
27639
- action: "/branch",
27640
- method: "post",
27641
- data,
27642
- operation: "Failed to create branch",
27643
- debug
27644
- });
27645
- }
27646
- async function deleteBranch2(params) {
27647
- const { apiKey, apiBaseUrl, instanceId, branchName, debug } = params;
27648
- if (!branchName)
27649
- throw new Error("branchName is required");
27650
- return callDblabApi2({
27651
- apiKey,
27652
- apiBaseUrl,
27653
- instanceId,
27654
- action: `/branch/${encodeURIComponent(branchName)}`,
27655
- method: "delete",
27656
- operation: "Failed to delete branch",
27657
- debug
27658
- });
27659
- }
27660
- async function branchLog2(params) {
27661
- const { apiKey, apiBaseUrl, instanceId, branchName, debug } = params;
27662
- if (!branchName)
27663
- throw new Error("branchName is required");
27664
- return callDblabApi2({
27665
- apiKey,
27666
- apiBaseUrl,
27667
- instanceId,
27668
- action: `/branch/${encodeURIComponent(branchName)}/log`,
27669
- method: "get",
27670
- operation: "Failed to fetch branch log",
27671
- debug
27672
- });
27673
- }
27674
- async function listSnapshots2(params) {
27675
- const { apiKey, apiBaseUrl, instanceId, branchName, dataset, debug } = params;
27676
- const qs = new URLSearchParams;
27677
- const branch = branchName?.trim();
27678
- if (branch)
27679
- qs.append("branch", branch);
27680
- if (dataset)
27681
- qs.append("dataset", dataset);
27682
- const action = `/snapshots${qs.toString() ? `?${qs.toString()}` : ""}`;
27683
- return callDblabApi2({
27684
- apiKey,
27685
- apiBaseUrl,
27686
- instanceId,
27687
- action,
27688
- method: "get",
27689
- operation: "Failed to list snapshots",
27690
- debug
27691
- });
27692
- }
27693
- async function createSnapshot2(params) {
27694
- const { apiKey, apiBaseUrl, instanceId, cloneId, message, debug } = params;
27695
- if (!cloneId)
27696
- throw new Error("cloneId is required");
27697
- const data = { cloneID: cloneId };
27698
- if (message)
27699
- data.message = message;
27700
- return callDblabApi2({
27701
- apiKey,
27702
- apiBaseUrl,
27703
- instanceId,
27704
- action: "/branch/snapshot",
27705
- method: "post",
27706
- data,
27707
- operation: "Failed to create snapshot",
27708
- debug
27709
- });
27710
- }
27711
- async function destroySnapshot2(params) {
27712
- const { apiKey, apiBaseUrl, instanceId, snapshotId, force, debug } = params;
27713
- if (!snapshotId)
27714
- throw new Error("snapshotId is required");
27715
- const action = `/snapshot/${snapshotId}?force=${Boolean(force)}`;
27716
- return callDblabApi2({
27717
- apiKey,
27718
- apiBaseUrl,
27719
- instanceId,
27720
- action,
27721
- method: "delete",
27722
- operation: "Failed to destroy snapshot",
27723
- debug
27724
- });
27725
- }
27726
-
27727
26341
  // bin/postgres-ai.ts
27728
26342
  init_util();
27729
26343
 
27730
26344
  // lib/instances.ts
27731
- import * as fs6 from "fs";
26345
+ import * as fs4 from "fs";
27732
26346
 
27733
26347
  // node_modules/js-yaml/dist/js-yaml.mjs
27734
26348
  /*! js-yaml 4.1.1 https://github.com/nodeca/js-yaml @license MIT */
@@ -30393,11 +29007,11 @@ class InstancesParseError extends Error {
30393
29007
  }
30394
29008
  }
30395
29009
  function loadInstances(file) {
30396
- if (!fs6.existsSync(file))
29010
+ if (!fs4.existsSync(file))
30397
29011
  return [];
30398
- if (fs6.lstatSync(file).isDirectory())
29012
+ if (fs4.lstatSync(file).isDirectory())
30399
29013
  return [];
30400
- const text = fs6.readFileSync(file, "utf8");
29014
+ const text = fs4.readFileSync(file, "utf8");
30401
29015
  if (text.trim() === "")
30402
29016
  return [];
30403
29017
  let parsed;
@@ -30575,8 +29189,8 @@ async function registerAasCollection(apiKey, instanceId, opts) {
30575
29189
  }
30576
29190
 
30577
29191
  // lib/storage.ts
30578
- import * as fs7 from "fs";
30579
- import * as path6 from "path";
29192
+ import * as fs5 from "fs";
29193
+ import * as path4 from "path";
30580
29194
  var MAX_UPLOAD_SIZE2 = 500 * 1024 * 1024;
30581
29195
  var MAX_DOWNLOAD_SIZE2 = 500 * 1024 * 1024;
30582
29196
  var MIME_TYPES2 = {
@@ -30617,11 +29231,11 @@ async function uploadFile2(params) {
30617
29231
  if (!filePath) {
30618
29232
  throw new Error("filePath is required");
30619
29233
  }
30620
- const resolvedPath = path6.resolve(filePath);
30621
- if (!fs7.existsSync(resolvedPath)) {
29234
+ const resolvedPath = path4.resolve(filePath);
29235
+ if (!fs5.existsSync(resolvedPath)) {
30622
29236
  throw new Error(`File not found: ${resolvedPath}`);
30623
29237
  }
30624
- const stat = fs7.statSync(resolvedPath);
29238
+ const stat = fs5.statSync(resolvedPath);
30625
29239
  if (!stat.isFile()) {
30626
29240
  throw new Error(`Not a file: ${resolvedPath}`);
30627
29241
  }
@@ -30633,9 +29247,9 @@ async function uploadFile2(params) {
30633
29247
  console.error("Warning: storage URL uses HTTP — API key will be sent unencrypted");
30634
29248
  }
30635
29249
  const url = `${base}/upload`;
30636
- const fileBuffer = fs7.readFileSync(resolvedPath);
30637
- const fileName = path6.basename(resolvedPath);
30638
- const ext = path6.extname(fileName).toLowerCase();
29250
+ const fileBuffer = fs5.readFileSync(resolvedPath);
29251
+ const fileName = path4.basename(resolvedPath);
29252
+ const ext = path4.extname(fileName).toLowerCase();
30639
29253
  const mimeType = MIME_TYPES2[ext] || "application/octet-stream";
30640
29254
  const formData = new FormData;
30641
29255
  formData.append("file", new Blob([fileBuffer], { type: mimeType }), fileName);
@@ -30694,15 +29308,15 @@ async function downloadFile2(params) {
30694
29308
  const relativePath = fileUrl.startsWith("/") ? fileUrl : `/${fileUrl}`;
30695
29309
  fullUrl = `${base}${relativePath}`;
30696
29310
  }
30697
- const urlFilename = path6.basename(new URL(fullUrl).pathname);
29311
+ const urlFilename = path4.basename(new URL(fullUrl).pathname);
30698
29312
  if (!urlFilename) {
30699
29313
  throw new Error("Cannot derive filename from URL; please specify --output");
30700
29314
  }
30701
- const saveTo = outputPath ? path6.resolve(outputPath) : path6.resolve(urlFilename);
29315
+ const saveTo = outputPath ? path4.resolve(outputPath) : path4.resolve(urlFilename);
30702
29316
  if (!outputPath) {
30703
- const normalizedSave = path6.normalize(saveTo);
30704
- const cwd = path6.normalize(process.cwd());
30705
- if (normalizedSave !== cwd && !normalizedSave.startsWith(cwd + path6.sep)) {
29317
+ const normalizedSave = path4.normalize(saveTo);
29318
+ const cwd = path4.normalize(process.cwd());
29319
+ if (normalizedSave !== cwd && !normalizedSave.startsWith(cwd + path4.sep)) {
30706
29320
  throw new Error("Derived output path escapes current directory; please specify --output");
30707
29321
  }
30708
29322
  }
@@ -30734,11 +29348,11 @@ async function downloadFile2(params) {
30734
29348
  }
30735
29349
  const arrayBuffer = await response.arrayBuffer();
30736
29350
  const buffer = Buffer.from(arrayBuffer);
30737
- const parentDir = path6.dirname(saveTo);
30738
- if (!fs7.existsSync(parentDir)) {
30739
- fs7.mkdirSync(parentDir, { recursive: true });
29351
+ const parentDir = path4.dirname(saveTo);
29352
+ if (!fs5.existsSync(parentDir)) {
29353
+ fs5.mkdirSync(parentDir, { recursive: true });
30740
29354
  }
30741
- fs7.writeFileSync(saveTo, buffer);
29355
+ fs5.writeFileSync(saveTo, buffer);
30742
29356
  return {
30743
29357
  savedTo: saveTo,
30744
29358
  size: buffer.length,
@@ -30750,9 +29364,9 @@ function buildMarkdownLink2(fileUrl, storageBaseUrl, filename) {
30750
29364
  const base = normalizeBaseUrl(storageBaseUrl);
30751
29365
  const normalizedFileUrl = fileUrl.startsWith("/") ? fileUrl : `/${fileUrl}`;
30752
29366
  const fullUrl = fileUrl.startsWith("http://") || fileUrl.startsWith("https://") ? fileUrl : `${base}${normalizedFileUrl}`;
30753
- const name = filename || path6.basename(new URL(fullUrl).pathname);
29367
+ const name = filename || path4.basename(new URL(fullUrl).pathname);
30754
29368
  const safeName = name.replace(/[\[\]()]/g, "\\$&");
30755
- const ext = path6.extname(name).toLowerCase();
29369
+ const ext = path4.extname(name).toLowerCase();
30756
29370
  if (IMAGE_EXTENSIONS2.has(ext)) {
30757
29371
  return `![${safeName}](${fullUrl})`;
30758
29372
  }
@@ -30798,8 +29412,8 @@ ${links}`;
30798
29412
  // lib/init.ts
30799
29413
  import { randomBytes } from "crypto";
30800
29414
  import { URL as URL2, fileURLToPath } from "url";
30801
- import * as fs8 from "fs";
30802
- import * as path7 from "path";
29415
+ import * as fs6 from "fs";
29416
+ import * as path5 from "path";
30803
29417
  var DEFAULT_MONITORING_USER = "postgres_ai_mon";
30804
29418
  var KNOWN_PROVIDERS = ["self-managed", "supabase"];
30805
29419
  var SKIP_ROLE_CREATION_PROVIDERS = ["supabase"];
@@ -30891,21 +29505,21 @@ async function connectWithSslFallback(ClientClass, adminConn, verbose) {
30891
29505
  }
30892
29506
  function sqlDir() {
30893
29507
  const currentFile = fileURLToPath(import.meta.url);
30894
- const currentDir = path7.dirname(currentFile);
29508
+ const currentDir = path5.dirname(currentFile);
30895
29509
  const candidates = [
30896
- path7.resolve(currentDir, "..", "sql"),
30897
- path7.resolve(currentDir, "..", "..", "sql")
29510
+ path5.resolve(currentDir, "..", "sql"),
29511
+ path5.resolve(currentDir, "..", "..", "sql")
30898
29512
  ];
30899
29513
  for (const candidate of candidates) {
30900
- if (fs8.existsSync(candidate)) {
29514
+ if (fs6.existsSync(candidate)) {
30901
29515
  return candidate;
30902
29516
  }
30903
29517
  }
30904
29518
  throw new Error(`SQL directory not found. Searched: ${candidates.join(", ")}`);
30905
29519
  }
30906
29520
  function loadSqlTemplate(filename) {
30907
- const p = path7.join(sqlDir(), filename);
30908
- return fs8.readFileSync(p, "utf8");
29521
+ const p = path5.join(sqlDir(), filename);
29522
+ return fs6.readFileSync(p, "utf8");
30909
29523
  }
30910
29524
  function applyTemplate(sql, vars) {
30911
29525
  return sql.replace(/\{\{([A-Z0-9_]+)\}\}/g, (_, key) => {
@@ -31441,10 +30055,28 @@ async function checkCurrentUserPermissions(client) {
31441
30055
 
31442
30056
  union all
31443
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
+
31444
30075
  select
31445
30076
  'postgres_ai.pg_statistic view exists' as permission_name,
31446
30077
  'optional' as status,
31447
30078
  case
30079
+ when to_regnamespace('postgres_ai') is null then null
31448
30080
  when not has_schema_privilege(current_user, 'postgres_ai', 'USAGE') then null
31449
30081
  else to_regclass('postgres_ai.pg_statistic') is not null
31450
30082
  end as granted
@@ -31455,6 +30087,7 @@ async function checkCurrentUserPermissions(client) {
31455
30087
  'select on postgres_ai.pg_statistic' as permission_name,
31456
30088
  'optional' as status,
31457
30089
  case
30090
+ when to_regnamespace('postgres_ai') is null then null
31458
30091
  when not has_schema_privilege(current_user, 'postgres_ai', 'USAGE') then null
31459
30092
  when to_regclass('postgres_ai.pg_statistic') is null then null
31460
30093
  else has_table_privilege(current_user, 'postgres_ai.pg_statistic', 'select')
@@ -31474,6 +30107,10 @@ async function checkCurrentUserPermissions(client) {
31474
30107
  when permission_name like 'select on pg_catalog.pg_index' then
31475
30108
  format('grant select on pg_catalog.pg_index to %I;', current_user)
31476
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)
31477
30114
  when permission_name = 'postgres_ai.pg_statistic view exists' and granted = false then
31478
30115
  '-- create postgres_ai.pg_statistic view (see setup script)'
31479
30116
  when permission_name = 'select on postgres_ai.pg_statistic' and granted = false then
@@ -31500,6 +30137,10 @@ function formatPermissionCheckMessages(result) {
31500
30137
  const warnings = [];
31501
30138
  const errors3 = [];
31502
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
+ }
31503
30144
  const fix = row.fix_command ? ` Fix: ${row.fix_command}` : "";
31504
30145
  warnings.push(`Warning: optional permission missing — ${row.permission_name}.${fix}`);
31505
30146
  }
@@ -31950,9 +30591,9 @@ function escapeLiteral2(value) {
31950
30591
  }
31951
30592
 
31952
30593
  // lib/pkce.ts
31953
- import * as crypto3 from "crypto";
30594
+ import * as crypto from "crypto";
31954
30595
  function generateRandomString(length = 64) {
31955
- const bytes = crypto3.randomBytes(length);
30596
+ const bytes = crypto.randomBytes(length);
31956
30597
  return base64URLEncode(bytes);
31957
30598
  }
31958
30599
  function base64URLEncode(buffer) {
@@ -31962,7 +30603,7 @@ function generateCodeVerifier() {
31962
30603
  return generateRandomString(32);
31963
30604
  }
31964
30605
  function generateCodeChallenge(verifier) {
31965
- const hash = crypto3.createHash("sha256").update(verifier).digest();
30606
+ const hash = crypto.createHash("sha256").update(verifier).digest();
31966
30607
  return base64URLEncode(hash);
31967
30608
  }
31968
30609
  function generateState() {
@@ -32191,8 +30832,8 @@ import { createInterface } from "readline";
32191
30832
  import * as childProcess from "child_process";
32192
30833
 
32193
30834
  // lib/checkup.ts
32194
- import * as fs9 from "fs";
32195
- import * as path8 from "path";
30835
+ import * as fs7 from "fs";
30836
+ import * as path6 from "path";
32196
30837
 
32197
30838
  // lib/metrics-embedded.ts
32198
30839
  var METRICS = {
@@ -33923,7 +32564,7 @@ function buildCheckInfoMap() {
33923
32564
  }
33924
32565
 
33925
32566
  // lib/checkup.ts
33926
- var __dirname = "/home/agent/workspace/postgresai-joe-v2/cli/lib";
32567
+ var __dirname = "/Users/nik/gitlab/postgresai-wt-joe-cli/cli/lib";
33927
32568
  var SECONDS_PER_DAY = 86400;
33928
32569
  var SECONDS_PER_HOUR = 3600;
33929
32570
  var SECONDS_PER_MINUTE = 60;
@@ -34409,7 +33050,7 @@ function createBaseReport(checkId, checkTitle, nodeName) {
34409
33050
  }
34410
33051
  function readTextFileSafe(p) {
34411
33052
  try {
34412
- const value = fs9.readFileSync(p, "utf8").trim();
33053
+ const value = fs7.readFileSync(p, "utf8").trim();
34413
33054
  return value || null;
34414
33055
  } catch {
34415
33056
  return null;
@@ -34422,8 +33063,8 @@ function resolveBuildTs() {
34422
33063
  if (fromFile)
34423
33064
  return fromFile;
34424
33065
  try {
34425
- const pkgRoot = path8.resolve(__dirname, "..");
34426
- const fromPkgFile = readTextFileSafe(path8.join(pkgRoot, "BUILD_TS"));
33066
+ const pkgRoot = path6.resolve(__dirname, "..");
33067
+ const fromPkgFile = readTextFileSafe(path6.join(pkgRoot, "BUILD_TS"));
34427
33068
  if (fromPkgFile)
34428
33069
  return fromPkgFile;
34429
33070
  } catch (err) {
@@ -34431,8 +33072,8 @@ function resolveBuildTs() {
34431
33072
  console.warn(`[resolveBuildTs] Warning: path resolution failed: ${errorMsg}`);
34432
33073
  }
34433
33074
  try {
34434
- const pkgJsonPath = path8.resolve(__dirname, "..", "package.json");
34435
- const st = fs9.statSync(pkgJsonPath);
33075
+ const pkgJsonPath = path6.resolve(__dirname, "..", "package.json");
33076
+ const st = fs7.statSync(pkgJsonPath);
34436
33077
  return st.mtime.toISOString();
34437
33078
  } catch (err) {
34438
33079
  if (process.env.DEBUG) {
@@ -34688,12 +33329,70 @@ async function generateF003(client, nodeName) {
34688
33329
  };
34689
33330
  return report;
34690
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
+ }
34691
33385
  async function generateF004(client, nodeName) {
34692
33386
  const report = createBaseReport("F004", "Autovacuum: heap bloat (estimated)", nodeName);
34693
33387
  const postgresVersion = await getPostgresVersion(client);
34694
33388
  const pgMajorVersion = parseInt(postgresVersion.server_major_ver, 10);
34695
33389
  let bloatedTables = [];
33390
+ let status = await getBloatCheckStatus(client);
34696
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
+ });
34697
33396
  const sql = getMetricSql(METRIC_NAMES.F004, pgMajorVersion);
34698
33397
  const bloatResult = await client.query(sql);
34699
33398
  const vacuumStatsResult = await client.query(`
@@ -34737,7 +33436,8 @@ async function generateF004(client, nodeName) {
34737
33436
  };
34738
33437
  });
34739
33438
  } catch (err) {
34740
- const errorMsg = err instanceof Error ? err.message : String(err);
33439
+ status = bloatErrorStatus(err);
33440
+ const errorMsg = status.error || "Unknown error";
34741
33441
  console.error(`[F004] Error estimating table bloat: ${errorMsg}`);
34742
33442
  if (errorMsg.includes("postgres_ai.")) {
34743
33443
  console.error(` Hint: Run "postgresai prepare-db <connection>" to create required objects.`);
@@ -34747,6 +33447,7 @@ async function generateF004(client, nodeName) {
34747
33447
  const totalCount = bloatedTables.length;
34748
33448
  const totalBloatSizeBytes = bloatedTables.reduce((sum, t) => sum + t.bloat_size, 0);
34749
33449
  const dbEntry = {
33450
+ status,
34750
33451
  bloated_tables: bloatedTables,
34751
33452
  total_count: totalCount,
34752
33453
  total_bloat_size_bytes: totalBloatSizeBytes,
@@ -34765,7 +33466,12 @@ async function generateF005(client, nodeName) {
34765
33466
  const postgresVersion = await getPostgresVersion(client);
34766
33467
  const pgMajorVersion = parseInt(postgresVersion.server_major_ver, 10);
34767
33468
  let bloatedIndexes = [];
33469
+ let status = await getBloatCheckStatus(client);
34768
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
+ });
34769
33475
  const sql = getMetricSql(METRIC_NAMES.F005, pgMajorVersion);
34770
33476
  const bloatResult = await client.query(sql);
34771
33477
  const vacuumStatsResult = await client.query(`
@@ -34814,7 +33520,8 @@ async function generateF005(client, nodeName) {
34814
33520
  };
34815
33521
  });
34816
33522
  } catch (err) {
34817
- const errorMsg = err instanceof Error ? err.message : String(err);
33523
+ status = bloatErrorStatus(err);
33524
+ const errorMsg = status.error || "Unknown error";
34818
33525
  console.error(`[F005] Error estimating index bloat: ${errorMsg}`);
34819
33526
  if (errorMsg.includes("postgres_ai.")) {
34820
33527
  console.error(` Hint: Run "postgresai prepare-db <connection>" to create required objects.`);
@@ -34824,6 +33531,7 @@ async function generateF005(client, nodeName) {
34824
33531
  const totalCount = bloatedIndexes.length;
34825
33532
  const totalBloatSizeBytes = bloatedIndexes.reduce((sum, idx) => sum + idx.bloat_size, 0);
34826
33533
  const dbEntry = {
33534
+ status,
34827
33535
  bloated_indexes: bloatedIndexes,
34828
33536
  total_count: totalCount,
34829
33537
  total_bloat_size_bytes: totalBloatSizeBytes,
@@ -35496,6 +34204,10 @@ function generateCheckSummary(checkId, report) {
35496
34204
  return summarizeF001(nodeData);
35497
34205
  case "F003":
35498
34206
  return summarizeF003(nodeData);
34207
+ case "F004":
34208
+ return summarizeBloat(nodeData, "table");
34209
+ case "F005":
34210
+ return summarizeBloat(nodeData, "index");
35499
34211
  case "G001":
35500
34212
  return summarizeG001(nodeData);
35501
34213
  case "G003":
@@ -35681,6 +34393,25 @@ function summarizeF003(nodeData) {
35681
34393
  }
35682
34394
  return { status: "warning", message: parts.join(", ") };
35683
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
+ }
35684
34415
  function summarizeG001(nodeData) {
35685
34416
  const data = nodeData?.data || {};
35686
34417
  const settingsCount = Object.keys(data).length;
@@ -35713,7 +34444,7 @@ function summarizeG003(nodeData) {
35713
34444
  }
35714
34445
 
35715
34446
  // lib/instances.ts
35716
- import * as fs10 from "fs";
34447
+ import * as fs8 from "fs";
35717
34448
  class InstancesParseError2 extends Error {
35718
34449
  constructor(file, cause) {
35719
34450
  const causeMsg = cause instanceof Error ? cause.message : String(cause);
@@ -35722,11 +34453,11 @@ class InstancesParseError2 extends Error {
35722
34453
  }
35723
34454
  }
35724
34455
  function loadInstances2(file) {
35725
- if (!fs10.existsSync(file))
34456
+ if (!fs8.existsSync(file))
35726
34457
  return [];
35727
- if (fs10.lstatSync(file).isDirectory())
34458
+ if (fs8.lstatSync(file).isDirectory())
35728
34459
  return [];
35729
- const text = fs10.readFileSync(file, "utf8");
34460
+ const text = fs8.readFileSync(file, "utf8");
35730
34461
  if (text.trim() === "")
35731
34462
  return [];
35732
34463
  let parsed;
@@ -35759,22 +34490,22 @@ function buildInstance(name, connStr) {
35759
34490
  };
35760
34491
  }
35761
34492
  function addInstanceToFile(file, instance) {
35762
- if (fs10.existsSync(file) && fs10.lstatSync(file).isDirectory()) {
35763
- fs10.rmSync(file, { recursive: true, force: true });
34493
+ if (fs8.existsSync(file) && fs8.lstatSync(file).isDirectory()) {
34494
+ fs8.rmSync(file, { recursive: true, force: true });
35764
34495
  }
35765
34496
  const existing = loadInstances2(file);
35766
34497
  if (existing.some((i3) => i3.name === instance.name)) {
35767
34498
  throw new Error(`Monitoring target '${instance.name}' already exists`);
35768
34499
  }
35769
34500
  existing.push(instance);
35770
- fs10.writeFileSync(file, dump2(existing), "utf8");
34501
+ fs8.writeFileSync(file, dump2(existing), "utf8");
35771
34502
  }
35772
34503
  function removeInstanceFromFile(file, name) {
35773
34504
  const instances = loadInstances2(file);
35774
34505
  const filtered = instances.filter((i3) => i3.name !== name);
35775
34506
  if (filtered.length === instances.length)
35776
34507
  return false;
35777
- fs10.writeFileSync(file, dump2(filtered), "utf8");
34508
+ fs8.writeFileSync(file, dump2(filtered), "utf8");
35778
34509
  return true;
35779
34510
  }
35780
34511
  function extractSslmode(connStr) {
@@ -35869,13 +34600,13 @@ function stripMatchingQuotes(value) {
35869
34600
  return trimmed;
35870
34601
  }
35871
34602
  var REQUIRED_ENV_KEYS = [
35872
- { key: "REPLICATOR_PASSWORD", defaultValue: () => crypto4.randomBytes(32).toString("hex"), introducedIn: "0.13" },
34603
+ { key: "REPLICATOR_PASSWORD", defaultValue: () => crypto2.randomBytes(32).toString("hex"), introducedIn: "0.13" },
35873
34604
  { key: "VM_AUTH_USERNAME", defaultValue: () => "vmauth", introducedIn: "0.15" },
35874
- { key: "VM_AUTH_PASSWORD", defaultValue: () => crypto4.randomBytes(18).toString("base64"), introducedIn: "0.15" }
34605
+ { key: "VM_AUTH_PASSWORD", defaultValue: () => crypto2.randomBytes(18).toString("base64"), introducedIn: "0.15" }
35875
34606
  ];
35876
34607
  function ensureRequiredEnvVars(projectDir) {
35877
- const envFile = path9.resolve(projectDir, ".env");
35878
- const existing = fs11.existsSync(envFile) ? fs11.readFileSync(envFile, "utf8") : "";
34608
+ const envFile = path7.resolve(projectDir, ".env");
34609
+ const existing = fs9.existsSync(envFile) ? fs9.readFileSync(envFile, "utf8") : "";
35879
34610
  const added = [];
35880
34611
  const appendLines = [];
35881
34612
  for (const spec of REQUIRED_ENV_KEYS) {
@@ -35894,7 +34625,7 @@ function ensureRequiredEnvVars(projectDir) {
35894
34625
  ` : "") + appendLines.join(`
35895
34626
  `) + `
35896
34627
  `;
35897
- fs11.writeFileSync(envFile, newContent, { encoding: "utf8", mode: 384 });
34628
+ fs9.writeFileSync(envFile, newContent, { encoding: "utf8", mode: 384 });
35898
34629
  return added;
35899
34630
  }
35900
34631
  async function execFilePromise(file, args) {
@@ -35959,7 +34690,7 @@ function expandHomePath(p) {
35959
34690
  if (s === "~")
35960
34691
  return os3.homedir();
35961
34692
  if (s.startsWith("~/") || s.startsWith("~\\")) {
35962
- return path9.join(os3.homedir(), s.slice(2));
34693
+ return path7.join(os3.homedir(), s.slice(2));
35963
34694
  }
35964
34695
  return s;
35965
34696
  }
@@ -36008,10 +34739,10 @@ function prepareOutputDirectory(outputOpt) {
36008
34739
  if (!outputOpt)
36009
34740
  return;
36010
34741
  const outputDir = expandHomePath(outputOpt);
36011
- const outputPath = path9.isAbsolute(outputDir) ? outputDir : path9.resolve(process.cwd(), outputDir);
36012
- if (!fs11.existsSync(outputPath)) {
34742
+ const outputPath = path7.isAbsolute(outputDir) ? outputDir : path7.resolve(process.cwd(), outputDir);
34743
+ if (!fs9.existsSync(outputPath)) {
36013
34744
  try {
36014
- fs11.mkdirSync(outputPath, { recursive: true });
34745
+ fs9.mkdirSync(outputPath, { recursive: true });
36015
34746
  } catch (e) {
36016
34747
  const errAny = e;
36017
34748
  const code = typeof errAny?.code === "string" ? errAny.code : "";
@@ -36037,9 +34768,15 @@ function prepareUploadConfig(opts, rootOpts, shouldUpload, uploadExplicitlyReque
36037
34768
  console.error("Tip: run 'postgresai auth' or pass --api-key / set PGAI_API_KEY");
36038
34769
  return null;
36039
34770
  }
36040
- console.error("Notice: no API key configured \u2014 results will NOT be uploaded to PostgresAI.");
36041
- console.error(" To upload: run 'postgresai auth login' or pass --api-key / set PGAI_API_KEY.");
36042
- 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
+ }
36043
34780
  return;
36044
34781
  }
36045
34782
  const cfg = readConfig();
@@ -36047,7 +34784,7 @@ function prepareUploadConfig(opts, rootOpts, shouldUpload, uploadExplicitlyReque
36047
34784
  let project = (opts.project || cfg.defaultProject || "").trim();
36048
34785
  let projectWasGenerated = false;
36049
34786
  if (!project) {
36050
- project = `project_${crypto4.randomBytes(6).toString("hex")}`;
34787
+ project = `project_${crypto2.randomBytes(6).toString("hex")}`;
36051
34788
  projectWasGenerated = true;
36052
34789
  try {
36053
34790
  writeConfig({ defaultProject: project });
@@ -36093,8 +34830,8 @@ async function uploadCheckupReports(uploadCfg, reports, spinner, logUpload) {
36093
34830
  }
36094
34831
  function writeReportFiles(reports, outputPath) {
36095
34832
  for (const [checkId, report] of Object.entries(reports)) {
36096
- const filePath = path9.join(outputPath, `${checkId}.json`);
36097
- fs11.writeFileSync(filePath, JSON.stringify(report, null, 2), "utf8");
34833
+ const filePath = path7.join(outputPath, `${checkId}.json`);
34834
+ fs9.writeFileSync(filePath, JSON.stringify(report, null, 2), "utf8");
36098
34835
  const title = report.checkTitle || checkId;
36099
34836
  console.log(`\u2713 ${checkId} ${title}: ${filePath}`);
36100
34837
  }
@@ -36139,7 +34876,7 @@ function getDefaultMonitoringProjectDir() {
36139
34876
  const override = process.env.PGAI_PROJECT_DIR;
36140
34877
  if (override && override.trim())
36141
34878
  return override.trim();
36142
- return path9.join(getConfigDir(), "monitoring");
34879
+ return path7.join(getConfigDir(), "monitoring");
36143
34880
  }
36144
34881
  async function downloadText(url) {
36145
34882
  const controller = new AbortController;
@@ -36156,12 +34893,12 @@ async function downloadText(url) {
36156
34893
  }
36157
34894
  async function ensureDefaultMonitoringProject() {
36158
34895
  const projectDir = getDefaultMonitoringProjectDir();
36159
- const composeFile = path9.resolve(projectDir, "docker-compose.yml");
36160
- const instancesFile = path9.resolve(projectDir, "instances.yml");
36161
- if (!fs11.existsSync(projectDir)) {
36162
- fs11.mkdirSync(projectDir, { recursive: true, mode: 448 });
34896
+ const composeFile = path7.resolve(projectDir, "docker-compose.yml");
34897
+ const instancesFile = path7.resolve(projectDir, "instances.yml");
34898
+ if (!fs9.existsSync(projectDir)) {
34899
+ fs9.mkdirSync(projectDir, { recursive: true, mode: 448 });
36163
34900
  }
36164
- if (!fs11.existsSync(composeFile)) {
34901
+ if (!fs9.existsSync(composeFile)) {
36165
34902
  const refs = [
36166
34903
  process.env.PGAI_PROJECT_REF,
36167
34904
  package_default.version,
@@ -36173,39 +34910,39 @@ async function ensureDefaultMonitoringProject() {
36173
34910
  const url = `https://gitlab.com/postgres-ai/postgres_ai/-/raw/${encodeURIComponent(ref)}/docker-compose.yml`;
36174
34911
  try {
36175
34912
  const text = await downloadText(url);
36176
- fs11.writeFileSync(composeFile, text, { encoding: "utf8", mode: 384 });
34913
+ fs9.writeFileSync(composeFile, text, { encoding: "utf8", mode: 384 });
36177
34914
  break;
36178
34915
  } catch (err) {
36179
34916
  lastErr = err;
36180
34917
  }
36181
34918
  }
36182
- if (!fs11.existsSync(composeFile)) {
34919
+ if (!fs9.existsSync(composeFile)) {
36183
34920
  const msg = lastErr instanceof Error ? lastErr.message : String(lastErr);
36184
34921
  throw new Error(`Failed to bootstrap docker-compose.yml: ${msg}`);
36185
34922
  }
36186
34923
  }
36187
- if (fs11.existsSync(instancesFile) && fs11.lstatSync(instancesFile).isDirectory()) {
36188
- fs11.rmSync(instancesFile, { recursive: true, force: true });
34924
+ if (fs9.existsSync(instancesFile) && fs9.lstatSync(instancesFile).isDirectory()) {
34925
+ fs9.rmSync(instancesFile, { recursive: true, force: true });
36189
34926
  }
36190
- if (!fs11.existsSync(instancesFile)) {
34927
+ if (!fs9.existsSync(instancesFile)) {
36191
34928
  const header = `# PostgreSQL instances to monitor
36192
34929
  ` + `# Add your instances using: pgai mon targets add <connection-string> <name>
36193
34930
 
36194
34931
  `;
36195
- fs11.writeFileSync(instancesFile, header, { encoding: "utf8", mode: 384 });
34932
+ fs9.writeFileSync(instancesFile, header, { encoding: "utf8", mode: 384 });
36196
34933
  }
36197
- const pgwatchConfig = path9.resolve(projectDir, ".pgwatch-config");
36198
- if (!fs11.existsSync(pgwatchConfig)) {
36199
- fs11.writeFileSync(pgwatchConfig, "", { encoding: "utf8", mode: 384 });
34934
+ const pgwatchConfig = path7.resolve(projectDir, ".pgwatch-config");
34935
+ if (!fs9.existsSync(pgwatchConfig)) {
34936
+ fs9.writeFileSync(pgwatchConfig, "", { encoding: "utf8", mode: 384 });
36200
34937
  }
36201
- const envFile = path9.resolve(projectDir, ".env");
36202
- if (!fs11.existsSync(envFile)) {
34938
+ const envFile = path7.resolve(projectDir, ".env");
34939
+ if (!fs9.existsSync(envFile)) {
36203
34940
  const envText = `PGAI_TAG=${package_default.version}
36204
34941
  # PGAI_REGISTRY=registry.gitlab.com/postgres-ai/postgres_ai
36205
34942
  `;
36206
- fs11.writeFileSync(envFile, envText, { encoding: "utf8", mode: 384 });
34943
+ fs9.writeFileSync(envFile, envText, { encoding: "utf8", mode: 384 });
36207
34944
  }
36208
- return { fs: fs11, path: path9, projectDir, composeFile, instancesFile };
34945
+ return { fs: fs9, path: path7, projectDir, composeFile, instancesFile };
36209
34946
  }
36210
34947
  function sanitizeTagForBackup(tag) {
36211
34948
  if (tag == null)
@@ -36214,10 +34951,10 @@ function sanitizeTagForBackup(tag) {
36214
34951
  return /^[A-Za-z0-9._-]{1,64}$/.test(stripped) ? stripped : null;
36215
34952
  }
36216
34953
  function readDeployedTag(projectDir) {
36217
- const envFile = path9.resolve(projectDir, ".env");
36218
- if (!fs11.existsSync(envFile))
34954
+ const envFile = path7.resolve(projectDir, ".env");
34955
+ if (!fs9.existsSync(envFile))
36219
34956
  return null;
36220
- const m = fs11.readFileSync(envFile, "utf8").match(/^PGAI_TAG=(.+)$/m);
34957
+ const m = fs9.readFileSync(envFile, "utf8").match(/^PGAI_TAG=(.+)$/m);
36221
34958
  return m ? sanitizeTagForBackup(m[1]) : null;
36222
34959
  }
36223
34960
  function isValidComposeYaml(text) {
@@ -36254,10 +34991,10 @@ function composeContentEqual(a, b) {
36254
34991
  return a.replace(/\s+$/, "") === b.replace(/\s+$/, "");
36255
34992
  }
36256
34993
  async function refreshBundledComposeIfStale(projectDir, oldTag) {
36257
- if (fs11.existsSync(path9.resolve(projectDir, ".git")))
34994
+ if (fs9.existsSync(path7.resolve(projectDir, ".git")))
36258
34995
  return false;
36259
- const composeFile = path9.resolve(projectDir, "docker-compose.yml");
36260
- if (!fs11.existsSync(composeFile))
34996
+ const composeFile = path7.resolve(projectDir, "docker-compose.yml");
34997
+ if (!fs9.existsSync(composeFile))
36261
34998
  return false;
36262
34999
  const refs = [
36263
35000
  process.env.PGAI_PROJECT_REF,
@@ -36270,24 +35007,24 @@ async function refreshBundledComposeIfStale(projectDir, oldTag) {
36270
35007
  console.error(" Keeping the existing compose. If dashboards are blank after upgrade, re-run this command once network is available.");
36271
35008
  return false;
36272
35009
  }
36273
- const existing = fs11.readFileSync(composeFile, "utf8");
35010
+ const existing = fs9.readFileSync(composeFile, "utf8");
36274
35011
  if (composeContentEqual(existing, fetched))
36275
35012
  return false;
36276
35013
  const deployedTag = sanitizeTagForBackup(oldTag ?? readDeployedTag(projectDir));
36277
35014
  const tagPart = deployedTag ?? new Date().toISOString().replace(/[:.]/g, "-");
36278
- const oldHash = crypto4.createHash("sha256").update(existing).digest("hex").slice(0, 8);
36279
- const backup = path9.resolve(projectDir, `docker-compose.yml.bak-${tagPart}-${oldHash}`);
35015
+ const oldHash = crypto2.createHash("sha256").update(existing).digest("hex").slice(0, 8);
35016
+ const backup = path7.resolve(projectDir, `docker-compose.yml.bak-${tagPart}-${oldHash}`);
36280
35017
  let backupName = null;
36281
35018
  try {
36282
- fs11.writeFileSync(backup, existing, { encoding: "utf8", mode: 384, flag: "wx" });
36283
- backupName = path9.basename(backup);
35019
+ fs9.writeFileSync(backup, existing, { encoding: "utf8", mode: 384, flag: "wx" });
35020
+ backupName = path7.basename(backup);
36284
35021
  } catch (err) {
36285
35022
  const e = err;
36286
35023
  if (e && e.code === "EEXIST") {
36287
- backupName = path9.basename(backup);
35024
+ backupName = path7.basename(backup);
36288
35025
  }
36289
35026
  }
36290
- fs11.writeFileSync(composeFile, fetched, { encoding: "utf8", mode: 384 });
35027
+ fs9.writeFileSync(composeFile, fetched, { encoding: "utf8", mode: 384 });
36291
35028
  const backupNote = backupName ? ` (backup: ${backupName})` : "";
36292
35029
  console.log(`\u2713 Refreshed docker-compose.yml to ${package_default.version}${backupNote}`);
36293
35030
  return true;
@@ -37264,7 +36001,7 @@ program2.command("unprepare-db [conn]").description("remove monitoring setup: dr
37264
36001
  closeReadline();
37265
36002
  }
37266
36003
  });
37267
- 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", [
37268
36005
  "",
37269
36006
  "Available checks:",
37270
36007
  ...Object.entries(CHECK_INFO).map(([id, title]) => ` ${id}: ${title}`),
@@ -37275,7 +36012,7 @@ program2.command("checkup [checkIdOrConn] [conn]").description("generate health
37275
36012
  " postgresai checkup postgresql://user:pass@host:5432/db --check-id H002",
37276
36013
  " postgresai checkup postgresql://user:pass@host:5432/db --output ./reports",
37277
36014
  " postgresai checkup postgresql://user:pass@host:5432/db --no-upload --json",
37278
- " postgresai checkup postgresql://user:pass@host:5432/db --no-upload --markdown"
36015
+ " postgresai checkup postgresql://user:pass@host:5432/db --markdown"
37279
36016
  ].join(`
37280
36017
  `)).action(async (checkIdOrConn, connArg, opts, cmd) => {
37281
36018
  const checkIdPattern = /^[A-Z]\d{3}$/i;
@@ -37315,6 +36052,13 @@ Usage: postgresai checkup ${checkId} postgresql://user@host:5432/dbname
37315
36052
  return;
37316
36053
  }
37317
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
+ }
37318
36062
  let shouldUpload = !uploadExplicitlyDisabled;
37319
36063
  const outputPath = prepareOutputDirectory(opts.output);
37320
36064
  if (outputPath === null) {
@@ -37497,7 +36241,9 @@ Usage: postgresai checkup ${checkId} postgresql://user@host:5432/dbname
37497
36241
  console.log(`
37498
36242
  For details:`);
37499
36243
  console.log(" --json Output JSON");
37500
- console.log(" --markdown Output markdown");
36244
+ if (!uploadExplicitlyDisabled) {
36245
+ console.log(" --markdown Output markdown via PostgresAI API");
36246
+ }
37501
36247
  console.log(" --output <dir> Save to directory");
37502
36248
  }
37503
36249
  } catch (error2) {
@@ -37521,12 +36267,12 @@ function resolvePaths() {
37521
36267
  const startDir = process.cwd();
37522
36268
  let currentDir = startDir;
37523
36269
  while (true) {
37524
- const composeFile = path9.resolve(currentDir, "docker-compose.yml");
37525
- if (fs11.existsSync(composeFile)) {
37526
- const instancesFile = path9.resolve(currentDir, "instances.yml");
37527
- return { fs: fs11, path: path9, projectDir: currentDir, composeFile, instancesFile };
36270
+ const composeFile = path7.resolve(currentDir, "docker-compose.yml");
36271
+ if (fs9.existsSync(composeFile)) {
36272
+ const instancesFile = path7.resolve(currentDir, "instances.yml");
36273
+ return { fs: fs9, path: path7, projectDir: currentDir, composeFile, instancesFile };
37528
36274
  }
37529
- const parentDir = path9.dirname(currentDir);
36275
+ const parentDir = path7.dirname(currentDir);
37530
36276
  if (parentDir === currentDir)
37531
36277
  break;
37532
36278
  currentDir = parentDir;
@@ -37658,10 +36404,10 @@ function resolveAdoptedProject(reg) {
37658
36404
  }
37659
36405
  function updatePgwatchConfig(configPath, updates) {
37660
36406
  let lines = [];
37661
- if (fs11.existsSync(configPath)) {
37662
- const stats = fs11.statSync(configPath);
36407
+ if (fs9.existsSync(configPath)) {
36408
+ const stats = fs9.statSync(configPath);
37663
36409
  if (!stats.isDirectory()) {
37664
- const content = fs11.readFileSync(configPath, "utf8");
36410
+ const content = fs9.readFileSync(configPath, "utf8");
37665
36411
  lines = content.split(/\r?\n/).filter((l) => l.trim() !== "");
37666
36412
  }
37667
36413
  }
@@ -37673,7 +36419,7 @@ function updatePgwatchConfig(configPath, updates) {
37673
36419
  lines.push(`${key}=${value}`);
37674
36420
  }
37675
36421
  }
37676
- fs11.writeFileSync(configPath, lines.join(`
36422
+ fs9.writeFileSync(configPath, lines.join(`
37677
36423
  `) + `
37678
36424
  `, { encoding: "utf8", mode: 384 });
37679
36425
  }
@@ -37711,12 +36457,12 @@ async function runCompose(args, grafanaPassword) {
37711
36457
  if (grafanaPassword) {
37712
36458
  env.GF_SECURITY_ADMIN_PASSWORD = grafanaPassword;
37713
36459
  } else {
37714
- const cfgPath = path9.resolve(projectDir, ".pgwatch-config");
37715
- if (fs11.existsSync(cfgPath)) {
36460
+ const cfgPath = path7.resolve(projectDir, ".pgwatch-config");
36461
+ if (fs9.existsSync(cfgPath)) {
37716
36462
  try {
37717
- const stats = fs11.statSync(cfgPath);
36463
+ const stats = fs9.statSync(cfgPath);
37718
36464
  if (!stats.isDirectory()) {
37719
- const content = fs11.readFileSync(cfgPath, "utf8");
36465
+ const content = fs9.readFileSync(cfgPath, "utf8");
37720
36466
  const match = content.match(/^grafana_password=([^\r\n]+)/m);
37721
36467
  if (match) {
37722
36468
  env.GF_SECURITY_ADMIN_PASSWORD = match[1].trim();
@@ -37729,10 +36475,10 @@ async function runCompose(args, grafanaPassword) {
37729
36475
  }
37730
36476
  }
37731
36477
  }
37732
- const envFilePath = path9.resolve(projectDir, ".env");
37733
- if (fs11.existsSync(envFilePath)) {
36478
+ const envFilePath = path7.resolve(projectDir, ".env");
36479
+ if (fs9.existsSync(envFilePath)) {
37734
36480
  try {
37735
- const envContent = fs11.readFileSync(envFilePath, "utf8");
36481
+ const envContent = fs9.readFileSync(envFilePath, "utf8");
37736
36482
  if (!env.VM_AUTH_USERNAME) {
37737
36483
  const m = envContent.match(/^VM_AUTH_USERNAME=([^\r\n]+)/m);
37738
36484
  if (m)
@@ -37789,20 +36535,20 @@ mon.command("local-install").description("install local monitoring stack (genera
37789
36535
  console.log(`Project directory: ${projectDir}
37790
36536
  `);
37791
36537
  if (opts.project) {
37792
- const cfgPath2 = path9.resolve(projectDir, ".pgwatch-config");
36538
+ const cfgPath2 = path7.resolve(projectDir, ".pgwatch-config");
37793
36539
  updatePgwatchConfig(cfgPath2, { project_name: opts.project });
37794
36540
  console.log(`Using project name: ${opts.project}
37795
36541
  `);
37796
36542
  }
37797
- const envFile = path9.resolve(projectDir, ".env");
36543
+ const envFile = path7.resolve(projectDir, ".env");
37798
36544
  let existingRegistry = null;
37799
36545
  let existingPassword = null;
37800
36546
  let existingReplicatorPassword = null;
37801
36547
  let existingVmAuthUsername = null;
37802
36548
  let existingVmAuthPassword = null;
37803
36549
  let previousTag = null;
37804
- if (fs11.existsSync(envFile)) {
37805
- const existingEnv = fs11.readFileSync(envFile, "utf8");
36550
+ if (fs9.existsSync(envFile)) {
36551
+ const existingEnv = fs9.readFileSync(envFile, "utf8");
37806
36552
  const previousTagMatch = existingEnv.match(/^PGAI_TAG=(.+)$/m);
37807
36553
  if (previousTagMatch)
37808
36554
  previousTag = previousTagMatch[1].trim();
@@ -37830,10 +36576,10 @@ mon.command("local-install").description("install local monitoring stack (genera
37830
36576
  if (existingPassword) {
37831
36577
  envLines.push(`GF_SECURITY_ADMIN_PASSWORD=${existingPassword}`);
37832
36578
  }
37833
- envLines.push(`REPLICATOR_PASSWORD=${existingReplicatorPassword || crypto4.randomBytes(32).toString("hex")}`);
36579
+ envLines.push(`REPLICATOR_PASSWORD=${existingReplicatorPassword || crypto2.randomBytes(32).toString("hex")}`);
37834
36580
  envLines.push(`VM_AUTH_USERNAME=${existingVmAuthUsername || "vmauth"}`);
37835
- envLines.push(`VM_AUTH_PASSWORD=${existingVmAuthPassword || crypto4.randomBytes(18).toString("base64")}`);
37836
- fs11.writeFileSync(envFile, envLines.join(`
36581
+ envLines.push(`VM_AUTH_PASSWORD=${existingVmAuthPassword || crypto2.randomBytes(18).toString("base64")}`);
36582
+ fs9.writeFileSync(envFile, envLines.join(`
37837
36583
  `) + `
37838
36584
  `, { encoding: "utf8", mode: 384 });
37839
36585
  await refreshBundledComposeIfStale(projectDir, previousTag);
@@ -37870,7 +36616,7 @@ Use demo mode without API key: postgres-ai mon local-install --demo`);
37870
36616
  if (apiKey) {
37871
36617
  console.log("Using API key provided via --api-key parameter");
37872
36618
  writeConfig({ apiKey });
37873
- updatePgwatchConfig(path9.resolve(projectDir, ".pgwatch-config"), { api_key: apiKey });
36619
+ updatePgwatchConfig(path7.resolve(projectDir, ".pgwatch-config"), { api_key: apiKey });
37874
36620
  console.log(`\u2713 API key saved
37875
36621
  `);
37876
36622
  } else if (opts.yes) {
@@ -37887,7 +36633,7 @@ Use demo mode without API key: postgres-ai mon local-install --demo`);
37887
36633
  const trimmedKey = inputApiKey.trim();
37888
36634
  if (trimmedKey) {
37889
36635
  writeConfig({ apiKey: trimmedKey });
37890
- updatePgwatchConfig(path9.resolve(projectDir, ".pgwatch-config"), { api_key: trimmedKey });
36636
+ updatePgwatchConfig(path7.resolve(projectDir, ".pgwatch-config"), { api_key: trimmedKey });
37891
36637
  apiKey = trimmedKey;
37892
36638
  console.log(`\u2713 API key saved
37893
36639
  `);
@@ -37921,7 +36667,7 @@ Use demo mode without API key: postgres-ai mon local-install --demo`);
37921
36667
  # Add your instances using: postgres-ai mon targets add
37922
36668
 
37923
36669
  `;
37924
- fs11.writeFileSync(instancesPath2, emptyInstancesContent, "utf8");
36670
+ fs9.writeFileSync(instancesPath2, emptyInstancesContent, "utf8");
37925
36671
  console.log(`Instances file: ${instancesPath2}`);
37926
36672
  console.log(`Project directory: ${projectDir2}
37927
36673
  `);
@@ -38025,17 +36771,17 @@ You can provide either:`);
38025
36771
  }
38026
36772
  } else {
38027
36773
  console.log("Step 2: Demo mode enabled - using included demo PostgreSQL database");
38028
- const currentDir = path9.dirname(fileURLToPath2(import.meta.url));
36774
+ const currentDir = path7.dirname(fileURLToPath2(import.meta.url));
38029
36775
  const demoCandidates = [
38030
- path9.resolve(currentDir, "..", "..", "instances.demo.yml"),
38031
- path9.resolve(currentDir, "..", "..", "..", "instances.demo.yml")
36776
+ path7.resolve(currentDir, "..", "..", "instances.demo.yml"),
36777
+ path7.resolve(currentDir, "..", "..", "..", "instances.demo.yml")
38032
36778
  ];
38033
- const demoSrc = demoCandidates.find((p) => fs11.existsSync(p));
36779
+ const demoSrc = demoCandidates.find((p) => fs9.existsSync(p));
38034
36780
  if (demoSrc) {
38035
- if (fs11.existsSync(instancesPath) && fs11.lstatSync(instancesPath).isDirectory()) {
38036
- fs11.rmSync(instancesPath, { recursive: true, force: true });
36781
+ if (fs9.existsSync(instancesPath) && fs9.lstatSync(instancesPath).isDirectory()) {
36782
+ fs9.rmSync(instancesPath, { recursive: true, force: true });
38037
36783
  }
38038
- fs11.copyFileSync(demoSrc, instancesPath);
36784
+ fs9.copyFileSync(demoSrc, instancesPath);
38039
36785
  console.log(`\u2713 Demo monitoring target configured
38040
36786
  `);
38041
36787
  } else {
@@ -38054,15 +36800,15 @@ Searched: ${demoCandidates.join(", ")}
38054
36800
  console.log(`\u2713 Configuration updated
38055
36801
  `);
38056
36802
  console.log(opts.demo ? "Step 4: Configuring Grafana security..." : "Step 4: Configuring Grafana security...");
38057
- const cfgPath = path9.resolve(projectDir, ".pgwatch-config");
36803
+ const cfgPath = path7.resolve(projectDir, ".pgwatch-config");
38058
36804
  let grafanaPassword = "";
38059
36805
  let vmAuthUsername = "";
38060
36806
  let vmAuthPassword = "";
38061
36807
  try {
38062
- if (fs11.existsSync(cfgPath)) {
38063
- const stats = fs11.statSync(cfgPath);
36808
+ if (fs9.existsSync(cfgPath)) {
36809
+ const stats = fs9.statSync(cfgPath);
38064
36810
  if (!stats.isDirectory()) {
38065
- const content = fs11.readFileSync(cfgPath, "utf8");
36811
+ const content = fs9.readFileSync(cfgPath, "utf8");
38066
36812
  const match = content.match(/^grafana_password=([^\r\n]+)/m);
38067
36813
  if (match) {
38068
36814
  grafanaPassword = match[1].trim();
@@ -38074,15 +36820,15 @@ Searched: ${demoCandidates.join(", ")}
38074
36820
  const { stdout: password } = await execFilePromise("openssl", ["rand", "-base64", "12"]);
38075
36821
  grafanaPassword = password.trim().replace(/\n/g, "");
38076
36822
  let configContent = "";
38077
- if (fs11.existsSync(cfgPath)) {
38078
- const stats = fs11.statSync(cfgPath);
36823
+ if (fs9.existsSync(cfgPath)) {
36824
+ const stats = fs9.statSync(cfgPath);
38079
36825
  if (!stats.isDirectory()) {
38080
- configContent = fs11.readFileSync(cfgPath, "utf8");
36826
+ configContent = fs9.readFileSync(cfgPath, "utf8");
38081
36827
  }
38082
36828
  }
38083
36829
  const lines = configContent.split(/\r?\n/).filter((l) => !/^grafana_password=/.test(l));
38084
36830
  lines.push(`grafana_password=${grafanaPassword}`);
38085
- fs11.writeFileSync(cfgPath, lines.filter(Boolean).join(`
36831
+ fs9.writeFileSync(cfgPath, lines.filter(Boolean).join(`
38086
36832
  `) + `
38087
36833
  `, "utf8");
38088
36834
  }
@@ -38095,9 +36841,9 @@ Searched: ${demoCandidates.join(", ")}
38095
36841
  grafanaPassword = "demo";
38096
36842
  }
38097
36843
  try {
38098
- const envFile2 = path9.resolve(projectDir, ".env");
38099
- if (fs11.existsSync(envFile2)) {
38100
- const envContent = fs11.readFileSync(envFile2, "utf8");
36844
+ const envFile2 = path7.resolve(projectDir, ".env");
36845
+ if (fs9.existsSync(envFile2)) {
36846
+ const envContent = fs9.readFileSync(envFile2, "utf8");
38101
36847
  const userMatch = envContent.match(/^VM_AUTH_USERNAME=([^\r\n]+)/m);
38102
36848
  const passMatch = envContent.match(/^VM_AUTH_PASSWORD=([^\r\n]+)/m);
38103
36849
  if (userMatch)
@@ -38113,13 +36859,13 @@ Searched: ${demoCandidates.join(", ")}
38113
36859
  vmAuthPassword = vmPass.trim().replace(/\n/g, "");
38114
36860
  }
38115
36861
  let envContent = "";
38116
- if (fs11.existsSync(envFile2)) {
38117
- envContent = fs11.readFileSync(envFile2, "utf8");
36862
+ if (fs9.existsSync(envFile2)) {
36863
+ envContent = fs9.readFileSync(envFile2, "utf8");
38118
36864
  }
38119
36865
  const envLines2 = envContent.split(/\r?\n/).filter((l) => !/^VM_AUTH_USERNAME=/.test(l) && !/^VM_AUTH_PASSWORD=/.test(l)).filter((l, i3, arr) => !(i3 === arr.length - 1 && l === ""));
38120
36866
  envLines2.push(`VM_AUTH_USERNAME=${vmAuthUsername}`);
38121
36867
  envLines2.push(`VM_AUTH_PASSWORD=${vmAuthPassword}`);
38122
- fs11.writeFileSync(envFile2, envLines2.join(`
36868
+ fs9.writeFileSync(envFile2, envLines2.join(`
38123
36869
  `) + `
38124
36870
  `, { encoding: "utf8", mode: 384 });
38125
36871
  }
@@ -38152,7 +36898,7 @@ Searched: ${demoCandidates.join(", ")}
38152
36898
  });
38153
36899
  const adoptedProject = resolveAdoptedProject(reg);
38154
36900
  if (adoptedProject != null) {
38155
- updatePgwatchConfig(path9.resolve(projectDir, ".pgwatch-config"), {
36901
+ updatePgwatchConfig(path7.resolve(projectDir, ".pgwatch-config"), {
38156
36902
  project_name: adoptedProject
38157
36903
  });
38158
36904
  const verb = reg?.created ? "Registered" : "Adopted";
@@ -38368,11 +37114,11 @@ mon.command("config").description("show monitoring services configuration").acti
38368
37114
  console.log(`Project Directory: ${projectDir}`);
38369
37115
  console.log(`Docker Compose File: ${composeFile}`);
38370
37116
  console.log(`Instances File: ${instancesFile}`);
38371
- if (fs11.existsSync(instancesFile) && !fs11.lstatSync(instancesFile).isDirectory()) {
37117
+ if (fs9.existsSync(instancesFile) && !fs9.lstatSync(instancesFile).isDirectory()) {
38372
37118
  console.log(`
38373
37119
  Instances configuration:
38374
37120
  `);
38375
- const text = fs11.readFileSync(instancesFile, "utf8");
37121
+ const text = fs9.readFileSync(instancesFile, "utf8");
38376
37122
  process.stdout.write(text);
38377
37123
  if (!/\n$/.test(text))
38378
37124
  console.log();
@@ -38421,8 +37167,8 @@ mon.command("update").description("update monitoring stack (migrate .env, pull i
38421
37167
  console.log("\u2713 .env is up to date");
38422
37168
  }
38423
37169
  console.log();
38424
- const gitDir = path9.resolve(projectDir, ".git");
38425
- if (fs11.existsSync(gitDir)) {
37170
+ const gitDir = path7.resolve(projectDir, ".git");
37171
+ if (fs9.existsSync(gitDir)) {
38426
37172
  console.log("Fetching latest changes...");
38427
37173
  await execFilePromise("git", ["fetch", "origin"]);
38428
37174
  const { stdout: branch } = await execFilePromise("git", ["rev-parse", "--abbrev-ref", "HEAD"]);
@@ -38577,7 +37323,7 @@ mon.command("check").description("monitoring services system readiness check").a
38577
37323
  var targets = mon.command("targets").description("manage databases to monitor");
38578
37324
  targets.command("list").description("list monitoring target databases").action(async () => {
38579
37325
  const { instancesFile: instancesPath, projectDir } = await resolveOrInitPaths();
38580
- if (!fs11.existsSync(instancesPath) || fs11.lstatSync(instancesPath).isDirectory()) {
37326
+ if (!fs9.existsSync(instancesPath) || fs9.lstatSync(instancesPath).isDirectory()) {
38581
37327
  console.error(`instances.yml not found in ${projectDir}`);
38582
37328
  process.exitCode = 1;
38583
37329
  return;
@@ -38640,7 +37386,7 @@ targets.command("add [connStr] [name]").description("add monitoring target datab
38640
37386
  });
38641
37387
  targets.command("remove <name>").description("remove monitoring target database").action(async (name) => {
38642
37388
  const { instancesFile: file } = await resolveOrInitPaths();
38643
- if (!fs11.existsSync(file) || fs11.lstatSync(file).isDirectory()) {
37389
+ if (!fs9.existsSync(file) || fs9.lstatSync(file).isDirectory()) {
38644
37390
  console.error("instances.yml not found");
38645
37391
  process.exitCode = 1;
38646
37392
  return;
@@ -38668,7 +37414,7 @@ targets.command("remove <name>").description("remove monitoring target database"
38668
37414
  });
38669
37415
  targets.command("test <name>").description("test monitoring target database connectivity").action(async (name) => {
38670
37416
  const { instancesFile: instancesPath } = await resolveOrInitPaths();
38671
- if (!fs11.existsSync(instancesPath) || fs11.lstatSync(instancesPath).isDirectory()) {
37417
+ if (!fs9.existsSync(instancesPath) || fs9.lstatSync(instancesPath).isDirectory()) {
38672
37418
  console.error("instances.yml not found");
38673
37419
  process.exitCode = 1;
38674
37420
  return;
@@ -38712,7 +37458,7 @@ targets.command("test <name>").description("test monitoring target database conn
38712
37458
  }
38713
37459
  });
38714
37460
  var auth = program2.command("auth").description("authentication and API key management");
38715
- 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) {
38716
37462
  if (opts.setKey) {
38717
37463
  const trimmedKey = opts.setKey.trim();
38718
37464
  if (!trimmedKey) {
@@ -38913,7 +37659,12 @@ Authentication failed: ${message}`);
38913
37659
  console.error(`Authentication error: ${message}`);
38914
37660
  process.exit(1);
38915
37661
  }
38916
- });
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);
38917
37668
  auth.command("show-key").description("show API key (masked)").action(async () => {
38918
37669
  const cfg = readConfig();
38919
37670
  if (!cfg.apiKey) {
@@ -38930,15 +37681,15 @@ To authenticate, run: pgai auth`);
38930
37681
  });
38931
37682
  auth.command("remove-key").description("remove API key").action(async () => {
38932
37683
  const newConfigPath = getConfigPath();
38933
- const hasNewConfig = fs11.existsSync(newConfigPath);
37684
+ const hasNewConfig = fs9.existsSync(newConfigPath);
38934
37685
  let legacyPath;
38935
37686
  try {
38936
37687
  const { projectDir } = await resolveOrInitPaths();
38937
- legacyPath = path9.resolve(projectDir, ".pgwatch-config");
37688
+ legacyPath = path7.resolve(projectDir, ".pgwatch-config");
38938
37689
  } catch {
38939
- legacyPath = path9.resolve(process.cwd(), ".pgwatch-config");
37690
+ legacyPath = path7.resolve(process.cwd(), ".pgwatch-config");
38940
37691
  }
38941
- const hasLegacyConfig = fs11.existsSync(legacyPath) && fs11.statSync(legacyPath).isFile();
37692
+ const hasLegacyConfig = fs9.existsSync(legacyPath) && fs9.statSync(legacyPath).isFile();
38942
37693
  if (!hasNewConfig && !hasLegacyConfig) {
38943
37694
  console.log("No API key configured");
38944
37695
  return;
@@ -38948,11 +37699,11 @@ auth.command("remove-key").description("remove API key").action(async () => {
38948
37699
  }
38949
37700
  if (hasLegacyConfig) {
38950
37701
  try {
38951
- const content = fs11.readFileSync(legacyPath, "utf8");
37702
+ const content = fs9.readFileSync(legacyPath, "utf8");
38952
37703
  const filtered = content.split(/\r?\n/).filter((l) => !/^api_key=/.test(l)).join(`
38953
37704
  `).replace(/\n+$/g, `
38954
37705
  `);
38955
- fs11.writeFileSync(legacyPath, filtered, "utf8");
37706
+ fs9.writeFileSync(legacyPath, filtered, "utf8");
38956
37707
  } catch (err) {
38957
37708
  console.warn(`Warning: Could not update legacy config: ${err instanceof Error ? err.message : String(err)}`);
38958
37709
  }
@@ -38963,7 +37714,7 @@ To authenticate again, run: pgai auth`);
38963
37714
  });
38964
37715
  mon.command("generate-grafana-password").description("generate Grafana password for monitoring services").action(async () => {
38965
37716
  const { projectDir } = await resolveOrInitPaths();
38966
- const cfgPath = path9.resolve(projectDir, ".pgwatch-config");
37717
+ const cfgPath = path7.resolve(projectDir, ".pgwatch-config");
38967
37718
  try {
38968
37719
  const { stdout: password } = await execFilePromise("openssl", ["rand", "-base64", "12"]);
38969
37720
  const newPassword = password.trim().replace(/\n/g, "");
@@ -38973,17 +37724,17 @@ mon.command("generate-grafana-password").description("generate Grafana password
38973
37724
  return;
38974
37725
  }
38975
37726
  let configContent = "";
38976
- if (fs11.existsSync(cfgPath)) {
38977
- const stats = fs11.statSync(cfgPath);
37727
+ if (fs9.existsSync(cfgPath)) {
37728
+ const stats = fs9.statSync(cfgPath);
38978
37729
  if (stats.isDirectory()) {
38979
37730
  console.error(".pgwatch-config is a directory, expected a file. Skipping read.");
38980
37731
  } else {
38981
- configContent = fs11.readFileSync(cfgPath, "utf8");
37732
+ configContent = fs9.readFileSync(cfgPath, "utf8");
38982
37733
  }
38983
37734
  }
38984
37735
  const lines = configContent.split(/\r?\n/).filter((l) => !/^grafana_password=/.test(l));
38985
37736
  lines.push(`grafana_password=${newPassword}`);
38986
- fs11.writeFileSync(cfgPath, lines.filter(Boolean).join(`
37737
+ fs9.writeFileSync(cfgPath, lines.filter(Boolean).join(`
38987
37738
  `) + `
38988
37739
  `, "utf8");
38989
37740
  console.log("\u2713 New Grafana password generated and saved");
@@ -39005,19 +37756,19 @@ Note: This command requires 'openssl' to be installed`);
39005
37756
  });
39006
37757
  mon.command("show-grafana-credentials").description("show Grafana credentials for monitoring services").action(async () => {
39007
37758
  const { projectDir } = await resolveOrInitPaths();
39008
- const cfgPath = path9.resolve(projectDir, ".pgwatch-config");
39009
- if (!fs11.existsSync(cfgPath)) {
37759
+ const cfgPath = path7.resolve(projectDir, ".pgwatch-config");
37760
+ if (!fs9.existsSync(cfgPath)) {
39010
37761
  console.error("Configuration file not found. Run 'postgres-ai mon local-install' first.");
39011
37762
  process.exitCode = 1;
39012
37763
  return;
39013
37764
  }
39014
- const stats = fs11.statSync(cfgPath);
37765
+ const stats = fs9.statSync(cfgPath);
39015
37766
  if (stats.isDirectory()) {
39016
37767
  console.error(".pgwatch-config is a directory, expected a file. Cannot read credentials.");
39017
37768
  process.exitCode = 1;
39018
37769
  return;
39019
37770
  }
39020
- const content = fs11.readFileSync(cfgPath, "utf8");
37771
+ const content = fs9.readFileSync(cfgPath, "utf8");
39021
37772
  const lines = content.split(/\r?\n/);
39022
37773
  let password = "";
39023
37774
  for (const line of lines) {
@@ -39037,9 +37788,9 @@ Grafana credentials:`);
39037
37788
  console.log(" URL: http://localhost:3000");
39038
37789
  console.log(" Username: monitor");
39039
37790
  console.log(` Password: ${password}`);
39040
- const envFile = path9.resolve(projectDir, ".env");
39041
- if (fs11.existsSync(envFile)) {
39042
- const envContent = fs11.readFileSync(envFile, "utf8");
37791
+ const envFile = path7.resolve(projectDir, ".env");
37792
+ if (fs9.existsSync(envFile)) {
37793
+ const envContent = fs9.readFileSync(envFile, "utf8");
39043
37794
  const vmUser = envContent.match(/^VM_AUTH_USERNAME=([^\r\n]+)/m);
39044
37795
  const vmPass = envContent.match(/^VM_AUTH_PASSWORD=([^\r\n]+)/m);
39045
37796
  if (vmUser && vmPass) {
@@ -39765,13 +38516,13 @@ reports.command("data [reportId]").description("get checkup report file data (in
39765
38516
  });
39766
38517
  spinner.stop();
39767
38518
  if (opts.output) {
39768
- const dir = path9.resolve(opts.output);
39769
- fs11.mkdirSync(dir, { recursive: true });
38519
+ const dir = path7.resolve(opts.output);
38520
+ fs9.mkdirSync(dir, { recursive: true });
39770
38521
  for (const f of result) {
39771
- const safeName = path9.basename(f.filename);
39772
- const filePath = path9.join(dir, safeName);
38522
+ const safeName = path7.basename(f.filename);
38523
+ const filePath = path7.join(dir, safeName);
39773
38524
  const content = f.type === "json" ? JSON.stringify(tryParseJson(f.data), null, 2) : f.data;
39774
- fs11.writeFileSync(filePath, content, "utf-8");
38525
+ fs9.writeFileSync(filePath, content, "utf-8");
39775
38526
  console.log(filePath);
39776
38527
  }
39777
38528
  } else if (opts.json) {
@@ -39816,62 +38567,45 @@ function tryParseJson(s) {
39816
38567
  return s;
39817
38568
  }
39818
38569
  }
39819
- function inferCommandFromResult(r) {
39820
- if (r.command)
39821
- return r.command;
39822
- if (r.plan_text !== undefined || r.plan_json !== undefined)
39823
- return "plan";
39824
- if (r.row_count !== undefined || r.result_rows !== undefined)
39825
- return "exec";
39826
- if (r.hypo_used !== undefined)
39827
- return "hypo";
39828
- if (r.terminated !== undefined)
39829
- return "terminate";
39830
- if (r.reset !== undefined)
39831
- return "reset";
39832
- if (r.snapshot !== undefined)
39833
- return "activity";
39834
- return null;
39835
- }
39836
- function printJoeOutcome(command, outcome, json3, budgetMs) {
39837
- const effectiveBudgetMs = typeof budgetMs === "number" && Number.isFinite(budgetMs) ? budgetMs : DEFAULT_BUDGET_MS2;
38570
+ function printJoeOutcome(outcome, json3, budgetMs) {
38571
+ const effectiveBudgetMs = typeof budgetMs === "number" && Number.isFinite(budgetMs) ? budgetMs : DEFAULT_BUDGET_MS;
39838
38572
  const budgetSeconds = Math.round(effectiveBudgetMs / 1000);
39839
38573
  if (outcome.budgetExpired) {
39840
38574
  if (json3) {
39841
38575
  console.log(JSON.stringify({
39842
38576
  command_id: outcome.commandId,
39843
38577
  status: outcome.status,
39844
- session_id: outcome.sessionId,
39845
38578
  budget_expired: true,
39846
38579
  resume: `pgai joe result ${outcome.commandId}`
39847
38580
  }, null, 2));
39848
38581
  } else {
39849
- console.log(`submitted ${outcome.commandId} \xB7 ${outcome.status} \xB7 budget ${budgetSeconds}s reached \u2014 resume: pgai joe result ${outcome.commandId}`);
38582
+ console.log(`started ${outcome.commandId} \xB7 ${outcome.status} \xB7 budget ${budgetSeconds}s reached \u2014 resume: pgai joe result ${outcome.commandId}`);
39850
38583
  }
39851
38584
  return;
39852
38585
  }
39853
- const result = outcome.result;
39854
- if (outcome.status === "done" && result) {
39855
- if (json3) {
39856
- console.log(JSON.stringify(result, null, 2));
39857
- } else {
39858
- console.log(`submitted ${outcome.commandId} \xB7 done`);
39859
- const body = formatJoeResult(command, result);
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);
39860
38598
  if (body)
39861
38599
  console.log(body);
39862
38600
  }
39863
38601
  return;
39864
38602
  }
39865
- if (outcome.status === "error") {
39866
- const msg = result?.error ?? "command failed";
39867
- console.error(`command ${outcome.commandId} error: ${msg}`);
39868
- process.exitCode = 1;
39869
- return;
39870
- }
39871
- console.error(`command ${outcome.commandId} timed out on the server \u2014 resume: pgai joe result ${outcome.commandId}`);
38603
+ if (json3 && output)
38604
+ console.log(JSON.stringify(output, null, 2));
38605
+ console.error(`command ${outcome.commandId} error: ${output?.error ?? "command failed"}`);
39872
38606
  process.exitCode = 1;
39873
38607
  }
39874
- async function runJoeCli(command, sql, args, opts) {
38608
+ async function runJoeCli(command, arg, opts) {
39875
38609
  try {
39876
38610
  const rootOpts = program2.opts();
39877
38611
  const cfg = readConfig();
@@ -39882,27 +38616,29 @@ async function runJoeCli(command, sql, args, opts) {
39882
38616
  return;
39883
38617
  }
39884
38618
  const projectRef = (opts.project ?? cfg.defaultProject ?? "").toString().trim();
39885
- if (!projectRef) {
39886
- console.error("Project is required. Pass --project <id|alias>.");
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).");
39887
38622
  process.exitCode = 1;
39888
38623
  return;
39889
38624
  }
39890
38625
  const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
39891
- const budgetMs = typeof opts.budget === "number" && !Number.isNaN(opts.budget) ? opts.budget * 1000 : undefined;
39892
- const outcome = await executeJoeCommand2({
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({
39893
38631
  apiKey,
39894
38632
  apiBaseUrl,
39895
38633
  command,
39896
- project: projectRef,
39897
- sql,
39898
- args,
39899
- session: opts.session ?? null,
39900
- newSession: !!opts.newSession,
38634
+ project: projectRef || undefined,
38635
+ instanceId: instanceRef || undefined,
38636
+ input: { arg, variant: opts.variant ?? null },
39901
38637
  orgId: cfg.orgId ?? undefined,
39902
38638
  budgetMs,
39903
38639
  debug: !!opts.debug
39904
38640
  });
39905
- printJoeOutcome(command, outcome, !!opts.json, budgetMs);
38641
+ printJoeOutcome(outcome, !!opts.json, budgetMs);
39906
38642
  } catch (err) {
39907
38643
  const message = err instanceof Error ? err.message : String(err);
39908
38644
  console.error(message);
@@ -39910,47 +38646,34 @@ async function runJoeCli(command, sql, args, opts) {
39910
38646
  }
39911
38647
  }
39912
38648
  function withJoeOptions(cmd) {
39913
- return cmd.option("--project <id|alias>", "target project by numeric id OR alias/name").option("--session <id>", "run on a specific session's clone").option("--new-session", "start a fresh session/clone (null session)").option("--budget <seconds>", "one-shot poll budget in seconds (default 25)", (v) => parseInt(v, 10)).option("--debug", "enable debug output").option("--json", "output raw JSON");
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");
39914
38650
  }
39915
- var joe = program2.command("joe").description("Joe API v2 \u2014 plan/EXPLAIN/exec queries on ephemeral DBLab clones");
38651
+ var joe = program2.command("joe").description("Joe \u2014 plan/EXPLAIN/exec queries on ephemeral DBLab clones");
39916
38652
  withJoeOptions(joe.command("plan <sql>").description("plan a query (EXPLAIN, plan-only \u2014 no execution; the fast/safe default)")).action(async (sql, opts) => {
39917
- await runJoeCli("plan", sql, null, opts);
38653
+ await runJoeCli("plan", sql, opts);
39918
38654
  });
39919
- withJoeOptions(joe.command("explain <sql>").description("EXPLAIN ANALYZE a query (EXECUTES on the hardened, ephemeral clone)")).action(async (sql, opts) => {
39920
- await runJoeCli("explain", sql, null, opts);
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);
39921
38657
  });
39922
38658
  withJoeOptions(joe.command("exec <sql>").description("run arbitrary DDL/DML on the clone (e.g. create index, analyze)")).action(async (sql, opts) => {
39923
- await runJoeCli("exec", sql, null, opts);
38659
+ await runJoeCli("exec", sql, opts);
39924
38660
  });
39925
- withJoeOptions(joe.command("hypo <indexSql>").description("HypoPG hypothetical index + re-plan (no real index, no data change)").requiredOption("--query <sql>", "target query the hypothetical index is evaluated against")).action(async (indexSql, opts) => {
39926
- await runJoeCli("hypo", indexSql, { query: opts.query }, opts);
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);
39927
38663
  });
39928
- withJoeOptions(joe.command("activity").description("running-activity snapshot (pg_stat_activity; query text redacted)")).action(async (opts) => {
39929
- await runJoeCli("activity", null, null, opts);
38664
+ withJoeOptions(joe.command("activity").description("running-activity snapshot (pg_stat_activity) on the clone")).action(async (opts) => {
38665
+ await runJoeCli("activity", null, opts);
39930
38666
  });
39931
38667
  withJoeOptions(joe.command("terminate <pid>").description("pg_terminate_backend(pid) on the clone")).action(async (pid, opts) => {
39932
- const pidStr = (pid ?? "").trim();
39933
- if (!/^[0-9]+$/.test(pidStr)) {
39934
- console.error("pid must be a number");
39935
- process.exitCode = 1;
39936
- return;
39937
- }
39938
- await runJoeCli("terminate", null, { pid: parseInt(pidStr, 10) }, opts);
38668
+ await runJoeCli("terminate", pid, opts);
39939
38669
  });
39940
38670
  withJoeOptions(joe.command("reset").description("reset/recreate the session's thin clone")).action(async (opts) => {
39941
- await runJoeCli("reset", null, null, opts);
38671
+ await runJoeCli("reset", null, opts);
39942
38672
  });
39943
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) => {
39944
- const args = { object: object4 };
39945
- if (opts.variant)
39946
- args.variant = opts.variant;
39947
- await runJoeCli("describe", null, args, opts);
39948
- });
39949
- withJoeOptions(joe.command("history <terms>").description("search prior Joe analyses, metadata-only (M1b \u2014 not yet available)")).action(async (_terms, _opts) => {
39950
- console.error("Joe history search is not available yet \u2014 it is planned for M1b. " + "No history-search backend is deployed; this command is a placeholder until then.");
39951
- process.exitCode = 1;
38674
+ await runJoeCli("describe", object4, opts);
39952
38675
  });
39953
- 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) => {
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) => {
39954
38677
  try {
39955
38678
  const rootOpts = program2.opts();
39956
38679
  const cfg = readConfig();
@@ -39961,78 +38684,25 @@ program2.command("projects").description("list the org's projects (shows which h
39961
38684
  return;
39962
38685
  }
39963
38686
  const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
39964
- const projects = await listProjects2({
39965
- apiKey,
39966
- apiBaseUrl,
39967
- orgId: cfg.orgId ?? undefined,
39968
- debug: !!opts.debug
39969
- });
38687
+ const output = await getCommandOutput({ apiKey, apiBaseUrl, commandId, debug: !!opts.debug });
39970
38688
  if (opts.json) {
39971
- console.log(JSON.stringify(projects, null, 2));
39972
- } else {
39973
- console.log(formatProjectsTable(projects));
39974
- }
39975
- } catch (err) {
39976
- const message = err instanceof Error ? err.message : String(err);
39977
- console.error(message);
39978
- process.exitCode = 1;
39979
- }
39980
- });
39981
- async function resolveDblabTarget(project, debug) {
39982
- const rootOpts = program2.opts();
39983
- const cfg = readConfig();
39984
- const { apiKey } = getConfig(rootOpts);
39985
- if (!apiKey) {
39986
- throw new Error("API key is required. Run 'pgai auth' first or set --api-key.");
39987
- }
39988
- const ref = (project ?? "").trim();
39989
- if (!ref) {
39990
- throw new Error("--project <id|alias> is required");
39991
- }
39992
- const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
39993
- const orgId = cfg.orgId ?? undefined;
39994
- const instanceId = await resolveDblabInstanceId2({ apiKey, apiBaseUrl, project: ref, orgId, debug });
39995
- return { apiKey, apiBaseUrl, orgId, instanceId };
39996
- }
39997
- var dblab = program2.command("dblab").description("DBLab thin-clone / branch / snapshot management (proxies the Platform DBLab API)");
39998
- var clone2 = dblab.command("clone").description("DBLab thin-clone management (proxies the Platform DBLab API)");
39999
- clone2.command("create").description("create a thin clone of the project's database").requiredOption("--project <id|alias>", "project id or alias").option("--branch <branch>", "branch to clone from").option("--snapshot <id>", "snapshot id to clone from").option("--id <id>", "clone id (DBLab generates one when omitted)").option("--db-user <user>", "clone DB user").option("--db-password <password>", "clone DB password").option("--protected", "protect the clone from auto-deletion").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (opts) => {
40000
- try {
40001
- const { apiKey, apiBaseUrl, instanceId } = await resolveDblabTarget(opts.project, !!opts.debug);
40002
- const result = await createClone2({
40003
- apiKey,
40004
- apiBaseUrl,
40005
- instanceId,
40006
- cloneId: opts.id,
40007
- branch: opts.branch,
40008
- snapshotId: opts.snapshot,
40009
- dbUser: opts.dbUser,
40010
- dbPassword: opts.dbPassword,
40011
- isProtected: !!opts.protected,
40012
- debug: !!opts.debug
40013
- });
40014
- printResult(result, opts.json);
40015
- } catch (err) {
40016
- console.error(err instanceof Error ? err.message : String(err));
40017
- process.exitCode = 1;
40018
- }
40019
- });
40020
- joe.command("status <commandId>").description("show a Joe command's status (metadata only)").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (commandId, opts) => {
40021
- try {
40022
- const rootOpts = program2.opts();
40023
- const cfg = readConfig();
40024
- const { apiKey } = getConfig(rootOpts);
40025
- if (!apiKey) {
40026
- console.error("API key is required. Run 'pgai auth' first or set --api-key.");
40027
- process.exitCode = 1;
38689
+ console.log(JSON.stringify(output, null, 2));
38690
+ if (output.status !== "ok") {
38691
+ process.exitCode = 1;
38692
+ }
40028
38693
  return;
40029
38694
  }
40030
- const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
40031
- const status = await getCommandStatus2({ apiKey, apiBaseUrl, commandId, debug: !!opts.debug });
40032
- if (opts.json) {
40033
- console.log(JSON.stringify(status, null, 2));
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;
40034
38703
  } else {
40035
- console.log(`command ${status.command_id} \xB7 ${status.status}${status.error ? ` \xB7 ${status.error}` : ""}`);
38704
+ console.error(`command ${output.command_id} \xB7 ${output.status} \u2014 result is not ready`);
38705
+ process.exitCode = 1;
40036
38706
  }
40037
38707
  } catch (err) {
40038
38708
  const message = err instanceof Error ? err.message : String(err);
@@ -40040,17 +38710,7 @@ joe.command("status <commandId>").description("show a Joe command's status (meta
40040
38710
  process.exitCode = 1;
40041
38711
  }
40042
38712
  });
40043
- clone2.command("list").description("list the project's thin clones").requiredOption("--project <id|alias>", "project id or alias").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (opts) => {
40044
- try {
40045
- const { apiKey, apiBaseUrl, instanceId } = await resolveDblabTarget(opts.project, !!opts.debug);
40046
- const result = await listClones2({ apiKey, apiBaseUrl, instanceId, debug: !!opts.debug });
40047
- printResult(result, opts.json);
40048
- } catch (err) {
40049
- console.error(err instanceof Error ? err.message : String(err));
40050
- process.exitCode = 1;
40051
- }
40052
- });
40053
- joe.command("result <commandId>").description("fetch a Joe command's result by id (resume a timed-out one-shot)").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (commandId, opts) => {
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) => {
40054
38714
  try {
40055
38715
  const rootOpts = program2.opts();
40056
38716
  const cfg = readConfig();
@@ -40061,28 +38721,16 @@ joe.command("result <commandId>").description("fetch a Joe command's result by i
40061
38721
  return;
40062
38722
  }
40063
38723
  const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
40064
- const result = await getCommandResult2({ apiKey, apiBaseUrl, commandId, debug: !!opts.debug });
38724
+ const projects = await listProjects({
38725
+ apiKey,
38726
+ apiBaseUrl,
38727
+ orgId: cfg.orgId ?? undefined,
38728
+ debug: !!opts.debug
38729
+ });
40065
38730
  if (opts.json) {
40066
- console.log(JSON.stringify(result, null, 2));
40067
- if (result.status === "error" || result.status === "timed_out") {
40068
- process.exitCode = 1;
40069
- }
40070
- return;
40071
- }
40072
- const inferred = inferCommandFromResult(result);
40073
- if (result.status === "done" && inferred) {
40074
- console.log(`command ${result.command_id} \xB7 done`);
40075
- const body = formatJoeResult(inferred, result);
40076
- if (body)
40077
- console.log(body);
40078
- } else if (result.status === "error") {
40079
- console.error(`command ${result.command_id} error: ${result.error ?? "command failed"}`);
40080
- process.exitCode = 1;
40081
- } else if (result.status === "timed_out") {
40082
- console.error(`command ${result.command_id} timed out on the server`);
40083
- process.exitCode = 1;
38731
+ console.log(JSON.stringify(projects, null, 2));
40084
38732
  } else {
40085
- console.log(`command ${result.command_id} \xB7 ${result.status}`);
38733
+ console.log(formatProjectsTable(projects));
40086
38734
  }
40087
38735
  } catch (err) {
40088
38736
  const message = err instanceof Error ? err.message : String(err);
@@ -40090,145 +38738,6 @@ joe.command("result <commandId>").description("fetch a Joe command's result by i
40090
38738
  process.exitCode = 1;
40091
38739
  }
40092
38740
  });
40093
- clone2.command("status <cloneId>").description("show a clone's status").requiredOption("--project <id|alias>", "project id or alias").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (cloneId, opts) => {
40094
- try {
40095
- const { apiKey, apiBaseUrl, instanceId } = await resolveDblabTarget(opts.project, !!opts.debug);
40096
- const result = await getClone2({ apiKey, apiBaseUrl, instanceId, cloneId, debug: !!opts.debug });
40097
- printResult(result, opts.json);
40098
- } catch (err) {
40099
- console.error(err instanceof Error ? err.message : String(err));
40100
- process.exitCode = 1;
40101
- }
40102
- });
40103
- clone2.command("reset <cloneId>").description("reset a clone to a pristine snapshot").requiredOption("--project <id|alias>", "project id or alias").option("--snapshot <id>", "snapshot id to reset to (default: latest)").option("--latest", "reset to the latest snapshot").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (cloneId, opts) => {
40104
- try {
40105
- const { apiKey, apiBaseUrl, instanceId } = await resolveDblabTarget(opts.project, !!opts.debug);
40106
- const result = await resetClone2({
40107
- apiKey,
40108
- apiBaseUrl,
40109
- instanceId,
40110
- cloneId,
40111
- snapshotId: opts.snapshot,
40112
- latest: opts.latest,
40113
- debug: !!opts.debug
40114
- });
40115
- printResult(result ?? { reset: true, cloneId }, opts.json);
40116
- } catch (err) {
40117
- console.error(err instanceof Error ? err.message : String(err));
40118
- process.exitCode = 1;
40119
- }
40120
- });
40121
- clone2.command("destroy <cloneId>").description("destroy a clone (requires joe:admin)").requiredOption("--project <id|alias>", "project id or alias").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (cloneId, opts) => {
40122
- try {
40123
- const { apiKey, apiBaseUrl, instanceId } = await resolveDblabTarget(opts.project, !!opts.debug);
40124
- const result = await destroyClone2({ apiKey, apiBaseUrl, instanceId, cloneId, debug: !!opts.debug });
40125
- printResult(result ?? { destroyed: true, cloneId }, opts.json);
40126
- } catch (err) {
40127
- console.error(err instanceof Error ? err.message : String(err));
40128
- process.exitCode = 1;
40129
- }
40130
- });
40131
- var branch = dblab.command("branch").description("DBLab branch management (proxies the Platform DBLab API)");
40132
- branch.command("list").description("list the project's branches").requiredOption("--project <id|alias>", "project id or alias").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (opts) => {
40133
- try {
40134
- const { apiKey, apiBaseUrl, instanceId } = await resolveDblabTarget(opts.project, !!opts.debug);
40135
- const result = await listBranches2({ apiKey, apiBaseUrl, instanceId, debug: !!opts.debug });
40136
- printResult(result, opts.json);
40137
- } catch (err) {
40138
- console.error(err instanceof Error ? err.message : String(err));
40139
- process.exitCode = 1;
40140
- }
40141
- });
40142
- branch.command("create <name>").description("create a branch").requiredOption("--project <id|alias>", "project id or alias").option("--snapshot <id>", "snapshot id to base the branch on").option("--base-branch <branch>", "parent branch to fork from").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (name, opts) => {
40143
- try {
40144
- const { apiKey, apiBaseUrl, instanceId } = await resolveDblabTarget(opts.project, !!opts.debug);
40145
- const result = await createBranch2({
40146
- apiKey,
40147
- apiBaseUrl,
40148
- instanceId,
40149
- branchName: name,
40150
- baseBranch: opts.baseBranch,
40151
- snapshotId: opts.snapshot,
40152
- debug: !!opts.debug
40153
- });
40154
- printResult(result, opts.json);
40155
- } catch (err) {
40156
- console.error(err instanceof Error ? err.message : String(err));
40157
- process.exitCode = 1;
40158
- }
40159
- });
40160
- branch.command("delete <name>").description("delete a branch (requires joe:admin)").requiredOption("--project <id|alias>", "project id or alias").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (name, opts) => {
40161
- try {
40162
- const { apiKey, apiBaseUrl, instanceId } = await resolveDblabTarget(opts.project, !!opts.debug);
40163
- const result = await deleteBranch2({ apiKey, apiBaseUrl, instanceId, branchName: name, debug: !!opts.debug });
40164
- printResult(result ?? { deleted: true, branch: name }, opts.json);
40165
- } catch (err) {
40166
- console.error(err instanceof Error ? err.message : String(err));
40167
- process.exitCode = 1;
40168
- }
40169
- });
40170
- branch.command("log <name>").description("show a branch's snapshot log").requiredOption("--project <id|alias>", "project id or alias").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (name, opts) => {
40171
- try {
40172
- const { apiKey, apiBaseUrl, instanceId } = await resolveDblabTarget(opts.project, !!opts.debug);
40173
- const result = await branchLog2({ apiKey, apiBaseUrl, instanceId, branchName: name, debug: !!opts.debug });
40174
- printResult(result, opts.json);
40175
- } catch (err) {
40176
- console.error(err instanceof Error ? err.message : String(err));
40177
- process.exitCode = 1;
40178
- }
40179
- });
40180
- var snapshot = dblab.command("snapshot").description("DBLab snapshot management (proxies the Platform DBLab API)");
40181
- snapshot.command("list").description("list the project's snapshots").requiredOption("--project <id|alias>", "project id or alias").option("--branch <branch>", "filter by branch").option("--dataset <dataset>", "filter by dataset").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (opts) => {
40182
- try {
40183
- const { apiKey, apiBaseUrl, instanceId } = await resolveDblabTarget(opts.project, !!opts.debug);
40184
- const result = await listSnapshots2({
40185
- apiKey,
40186
- apiBaseUrl,
40187
- instanceId,
40188
- branchName: opts.branch,
40189
- dataset: opts.dataset,
40190
- debug: !!opts.debug
40191
- });
40192
- printResult(result, opts.json);
40193
- } catch (err) {
40194
- console.error(err instanceof Error ? err.message : String(err));
40195
- process.exitCode = 1;
40196
- }
40197
- });
40198
- snapshot.command("create").description("create a snapshot from a clone").requiredOption("--project <id|alias>", "project id or alias").requiredOption("--clone <id>", "clone id to snapshot").option("--message <message>", "snapshot message").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (opts) => {
40199
- try {
40200
- const { apiKey, apiBaseUrl, instanceId } = await resolveDblabTarget(opts.project, !!opts.debug);
40201
- const result = await createSnapshot2({
40202
- apiKey,
40203
- apiBaseUrl,
40204
- instanceId,
40205
- cloneId: opts.clone,
40206
- message: opts.message,
40207
- debug: !!opts.debug
40208
- });
40209
- printResult(result, opts.json);
40210
- } catch (err) {
40211
- console.error(err instanceof Error ? err.message : String(err));
40212
- process.exitCode = 1;
40213
- }
40214
- });
40215
- snapshot.command("destroy <snapshotId>").description("destroy a snapshot (requires joe:admin)").requiredOption("--project <id|alias>", "project id or alias").option("--force", "force-delete even when dependent clones exist").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (snapshotId, opts) => {
40216
- try {
40217
- const { apiKey, apiBaseUrl, instanceId } = await resolveDblabTarget(opts.project, !!opts.debug);
40218
- const result = await destroySnapshot2({
40219
- apiKey,
40220
- apiBaseUrl,
40221
- instanceId,
40222
- snapshotId,
40223
- force: !!opts.force,
40224
- debug: !!opts.debug
40225
- });
40226
- printResult(result ?? { destroyed: true, snapshot: snapshotId }, opts.json);
40227
- } catch (err) {
40228
- console.error(err instanceof Error ? err.message : String(err));
40229
- process.exitCode = 1;
40230
- }
40231
- });
40232
38741
  var mcp = program2.command("mcp").description("MCP server integration");
40233
38742
  mcp.command("start").description("start MCP stdio server").option("--debug", "enable debug output").action(async (opts) => {
40234
38743
  const rootOpts = program2.opts();
@@ -40302,29 +38811,29 @@ mcp.command("install [client]").description("install MCP server configuration fo
40302
38811
  let configDir;
40303
38812
  switch (client) {
40304
38813
  case "cursor":
40305
- configPath = path9.join(homeDir, ".cursor", "mcp.json");
40306
- configDir = path9.dirname(configPath);
38814
+ configPath = path7.join(homeDir, ".cursor", "mcp.json");
38815
+ configDir = path7.dirname(configPath);
40307
38816
  break;
40308
38817
  case "windsurf":
40309
- configPath = path9.join(homeDir, ".windsurf", "mcp.json");
40310
- configDir = path9.dirname(configPath);
38818
+ configPath = path7.join(homeDir, ".windsurf", "mcp.json");
38819
+ configDir = path7.dirname(configPath);
40311
38820
  break;
40312
38821
  case "codex":
40313
- configPath = path9.join(homeDir, ".codex", "mcp.json");
40314
- configDir = path9.dirname(configPath);
38822
+ configPath = path7.join(homeDir, ".codex", "mcp.json");
38823
+ configDir = path7.dirname(configPath);
40315
38824
  break;
40316
38825
  default:
40317
38826
  console.error(`Configuration not implemented for: ${client}`);
40318
38827
  process.exitCode = 1;
40319
38828
  return;
40320
38829
  }
40321
- if (!fs11.existsSync(configDir)) {
40322
- fs11.mkdirSync(configDir, { recursive: true });
38830
+ if (!fs9.existsSync(configDir)) {
38831
+ fs9.mkdirSync(configDir, { recursive: true });
40323
38832
  }
40324
38833
  let config2 = { mcpServers: {} };
40325
- if (fs11.existsSync(configPath)) {
38834
+ if (fs9.existsSync(configPath)) {
40326
38835
  try {
40327
- const content = fs11.readFileSync(configPath, "utf8");
38836
+ const content = fs9.readFileSync(configPath, "utf8");
40328
38837
  config2 = JSON.parse(content);
40329
38838
  if (!config2.mcpServers) {
40330
38839
  config2.mcpServers = {};
@@ -40337,7 +38846,7 @@ mcp.command("install [client]").description("install MCP server configuration fo
40337
38846
  command: pgaiPath,
40338
38847
  args: ["mcp", "start"]
40339
38848
  };
40340
- fs11.writeFileSync(configPath, JSON.stringify(config2, null, 2), "utf8");
38849
+ fs9.writeFileSync(configPath, JSON.stringify(config2, null, 2), "utf8");
40341
38850
  console.log(`\u2713 PostgresAI MCP server configured for ${client}`);
40342
38851
  console.log(` Config file: ${configPath}`);
40343
38852
  console.log("");
@@ -40359,6 +38868,5 @@ export {
40359
38868
  refreshBundledComposeIfStale,
40360
38869
  readDeployedTag,
40361
38870
  planMonitoringRegistration,
40362
- isValidComposeYaml,
40363
- inferCommandFromResult
38871
+ isValidComposeYaml
40364
38872
  };