postgresai 0.16.0-dev.2 → 0.16.0-dev.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -9980,8 +9980,8 @@ var require_utils3 = __commonJS((exports, module) => {
9980
9980
  }
9981
9981
  return ind;
9982
9982
  }
9983
- function removeDotSegments(path4) {
9984
- let input = path4;
9983
+ function removeDotSegments(path5) {
9984
+ let input = path5;
9985
9985
  const output = [];
9986
9986
  let nextSlash = -1;
9987
9987
  let len = 0;
@@ -10171,8 +10171,8 @@ var require_schemes = __commonJS((exports, module) => {
10171
10171
  wsComponent.secure = undefined;
10172
10172
  }
10173
10173
  if (wsComponent.resourceName) {
10174
- const [path4, query] = wsComponent.resourceName.split("?");
10175
- wsComponent.path = path4 && path4 !== "/" ? path4 : undefined;
10174
+ const [path5, query] = wsComponent.resourceName.split("?");
10175
+ wsComponent.path = path5 && path5 !== "/" ? path5 : undefined;
10176
10176
  wsComponent.query = query;
10177
10177
  wsComponent.resourceName = undefined;
10178
10178
  }
@@ -13307,12 +13307,12 @@ var require_dist2 = __commonJS((exports, module) => {
13307
13307
  throw new Error(`Unknown format "${name}"`);
13308
13308
  return f;
13309
13309
  };
13310
- function addFormats(ajv, list, fs4, exportName) {
13310
+ function addFormats(ajv, list, fs5, exportName) {
13311
13311
  var _a2;
13312
13312
  var _b;
13313
13313
  (_a2 = (_b = ajv.opts.code).formats) !== null && _a2 !== undefined || (_b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`);
13314
13314
  for (const f of list)
13315
- ajv.addFormat(f, fs4[f]);
13315
+ ajv.addFormat(f, fs5[f]);
13316
13316
  }
13317
13317
  module.exports = exports = formatsPlugin;
13318
13318
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -13425,7 +13425,7 @@ var {
13425
13425
  // package.json
13426
13426
  var package_default = {
13427
13427
  name: "postgresai",
13428
- version: "0.16.0-dev.2",
13428
+ version: "0.16.0-dev.3",
13429
13429
  description: "postgres_ai CLI",
13430
13430
  license: "Apache-2.0",
13431
13431
  private: false,
@@ -16236,11 +16236,11 @@ var safeLoadAll = renamed("safeLoadAll", "loadAll");
16236
16236
  var safeDump = renamed("safeDump", "dump");
16237
16237
 
16238
16238
  // bin/postgres-ai.ts
16239
- import * as fs9 from "fs";
16240
- import * as path7 from "path";
16239
+ import * as fs11 from "fs";
16240
+ import * as path9 from "path";
16241
16241
  import * as os3 from "os";
16242
16242
  import { fileURLToPath as fileURLToPath2 } from "url";
16243
- import * as crypto2 from "crypto";
16243
+ import * as crypto4 from "crypto";
16244
16244
 
16245
16245
  // node_modules/pg/esm/index.mjs
16246
16246
  var import_lib = __toESM(require_lib2(), 1);
@@ -16256,7 +16256,7 @@ var Result = import_lib.default.Result;
16256
16256
  var TypeOverrides = import_lib.default.TypeOverrides;
16257
16257
  var defaults = import_lib.default.defaults;
16258
16258
  // package.json
16259
- var version = "0.16.0-dev.2";
16259
+ var version = "0.16.0-dev.3";
16260
16260
  var package_default2 = {
16261
16261
  name: "postgresai",
16262
16262
  version,
@@ -17267,6 +17267,305 @@ async function fetchReportFileData(params) {
17267
17267
  }
17268
17268
  }
17269
17269
 
17270
+ // lib/dblab.ts
17271
+ function isNumericProjectRef(ref) {
17272
+ return /^[0-9]+$/.test(ref.trim());
17273
+ }
17274
+ async function resolveDblabInstanceId(params) {
17275
+ const { apiKey, apiBaseUrl, orgId, debug } = params;
17276
+ if (!apiKey) {
17277
+ throw new Error("API key is required");
17278
+ }
17279
+ const ref = String(params.project ?? "").trim();
17280
+ if (!ref) {
17281
+ throw new Error("project is required (--project <id|alias>)");
17282
+ }
17283
+ const base = normalizeBaseUrl(apiBaseUrl);
17284
+ const url = new URL(`${base}/dblab_instances`);
17285
+ url.searchParams.set("select", "id,project_id,project_name,project_alias,project_label_or_name,org_id");
17286
+ if (typeof orgId === "number") {
17287
+ url.searchParams.set("org_id", `eq.${orgId}`);
17288
+ }
17289
+ const headers = {
17290
+ "access-token": apiKey,
17291
+ "Content-Type": "application/json",
17292
+ Connection: "close"
17293
+ };
17294
+ if (debug) {
17295
+ const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
17296
+ console.error(`Debug: GET URL: ${url.toString()}`);
17297
+ console.error(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
17298
+ }
17299
+ const response = await fetch(url.toString(), { method: "GET", headers });
17300
+ const text = await response.text();
17301
+ if (debug) {
17302
+ console.error(`Debug: Response status: ${response.status}`);
17303
+ console.error(`Debug: Response body: ${text}`);
17304
+ }
17305
+ if (!response.ok) {
17306
+ throw new Error(formatHttpError("Failed to resolve project's DBLab instance", response.status, text));
17307
+ }
17308
+ let rows;
17309
+ try {
17310
+ const parsed = JSON.parse(text);
17311
+ rows = Array.isArray(parsed) ? parsed : [];
17312
+ } catch {
17313
+ throw new Error(`Failed to parse DBLab instances response: ${text}`);
17314
+ }
17315
+ const numeric = isNumericProjectRef(ref);
17316
+ const needle = ref.toLowerCase();
17317
+ const match = rows.find((r) => {
17318
+ if (numeric) {
17319
+ return String(r.project_id ?? "") === ref;
17320
+ }
17321
+ return r.project_alias != null && String(r.project_alias).toLowerCase() === needle || r.project_name != null && String(r.project_name).toLowerCase() === needle || r.project_label_or_name != null && String(r.project_label_or_name).toLowerCase() === needle;
17322
+ });
17323
+ if (!match) {
17324
+ throw new Error(`No DBLab instance found for project '${ref}'. Run 'pgai projects' to see available projects.`);
17325
+ }
17326
+ return String(match.id);
17327
+ }
17328
+ async function callDblabApi(params) {
17329
+ const { apiKey, apiBaseUrl, instanceId, action, method, data, operation, debug } = params;
17330
+ if (!apiKey) {
17331
+ throw new Error("API key is required");
17332
+ }
17333
+ if (!instanceId) {
17334
+ throw new Error("instanceId is required");
17335
+ }
17336
+ const base = normalizeBaseUrl(apiBaseUrl);
17337
+ const url = new URL(`${base}/rpc/dblab_api_call`);
17338
+ const bodyObj = {
17339
+ instance_id: instanceId,
17340
+ action,
17341
+ method
17342
+ };
17343
+ if (data !== undefined) {
17344
+ bodyObj.data = data;
17345
+ }
17346
+ const body = JSON.stringify(bodyObj);
17347
+ const headers = {
17348
+ "access-token": apiKey,
17349
+ "Content-Type": "application/json",
17350
+ Connection: "close"
17351
+ };
17352
+ if (debug) {
17353
+ const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
17354
+ console.error(`Debug: POST URL: ${url.toString()}`);
17355
+ console.error(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
17356
+ console.error(`Debug: Request body: ${body}`);
17357
+ }
17358
+ const response = await fetch(url.toString(), { method: "POST", headers, body });
17359
+ const text = await response.text();
17360
+ if (debug) {
17361
+ console.error(`Debug: Response status: ${response.status}`);
17362
+ console.error(`Debug: Response body: ${text}`);
17363
+ }
17364
+ if (!response.ok) {
17365
+ throw new Error(formatHttpError(operation, response.status, text));
17366
+ }
17367
+ if (text.trim() === "") {
17368
+ return null;
17369
+ }
17370
+ try {
17371
+ return JSON.parse(text);
17372
+ } catch {
17373
+ throw new Error(`${operation}: failed to parse response: ${text}`);
17374
+ }
17375
+ }
17376
+ async function createClone(params) {
17377
+ const { apiKey, apiBaseUrl, instanceId, cloneId, branch, snapshotId, dbUser, dbPassword, isProtected, debug } = params;
17378
+ const data = { protected: Boolean(isProtected) };
17379
+ if (cloneId)
17380
+ data.id = cloneId;
17381
+ if (branch)
17382
+ data.branch = branch;
17383
+ if (snapshotId)
17384
+ data.snapshot = { id: snapshotId };
17385
+ if (dbUser && dbPassword)
17386
+ data.db = { username: dbUser, password: dbPassword };
17387
+ return callDblabApi({
17388
+ apiKey,
17389
+ apiBaseUrl,
17390
+ instanceId,
17391
+ action: "/clone",
17392
+ method: "post",
17393
+ data,
17394
+ operation: "Failed to create clone",
17395
+ debug
17396
+ });
17397
+ }
17398
+ async function listClones(params) {
17399
+ const { apiKey, apiBaseUrl, instanceId, debug } = params;
17400
+ return callDblabApi({
17401
+ apiKey,
17402
+ apiBaseUrl,
17403
+ instanceId,
17404
+ action: "/clones",
17405
+ method: "get",
17406
+ operation: "Failed to list clones",
17407
+ debug
17408
+ });
17409
+ }
17410
+ async function getClone(params) {
17411
+ const { apiKey, apiBaseUrl, instanceId, cloneId, debug } = params;
17412
+ if (!cloneId)
17413
+ throw new Error("cloneId is required");
17414
+ return callDblabApi({
17415
+ apiKey,
17416
+ apiBaseUrl,
17417
+ instanceId,
17418
+ action: `/clone/${encodeURIComponent(cloneId)}`,
17419
+ method: "get",
17420
+ operation: "Failed to get clone",
17421
+ debug
17422
+ });
17423
+ }
17424
+ async function resetClone(params) {
17425
+ const { apiKey, apiBaseUrl, instanceId, cloneId, snapshotId, latest, debug } = params;
17426
+ if (!cloneId)
17427
+ throw new Error("cloneId is required");
17428
+ const data = { latest: latest ?? !snapshotId };
17429
+ if (snapshotId)
17430
+ data.snapshotID = snapshotId;
17431
+ return callDblabApi({
17432
+ apiKey,
17433
+ apiBaseUrl,
17434
+ instanceId,
17435
+ action: `/clone/${encodeURIComponent(cloneId)}/reset`,
17436
+ method: "post",
17437
+ data,
17438
+ operation: "Failed to reset clone",
17439
+ debug
17440
+ });
17441
+ }
17442
+ async function destroyClone(params) {
17443
+ const { apiKey, apiBaseUrl, instanceId, cloneId, debug } = params;
17444
+ if (!cloneId)
17445
+ throw new Error("cloneId is required");
17446
+ return callDblabApi({
17447
+ apiKey,
17448
+ apiBaseUrl,
17449
+ instanceId,
17450
+ action: `/clone/${encodeURIComponent(cloneId)}`,
17451
+ method: "delete",
17452
+ operation: "Failed to destroy clone",
17453
+ debug
17454
+ });
17455
+ }
17456
+ async function listBranches(params) {
17457
+ const { apiKey, apiBaseUrl, instanceId, debug } = params;
17458
+ return callDblabApi({
17459
+ apiKey,
17460
+ apiBaseUrl,
17461
+ instanceId,
17462
+ action: "/branches",
17463
+ method: "get",
17464
+ operation: "Failed to list branches",
17465
+ debug
17466
+ });
17467
+ }
17468
+ async function createBranch(params) {
17469
+ const { apiKey, apiBaseUrl, instanceId, branchName, baseBranch, snapshotId, debug } = params;
17470
+ if (!branchName)
17471
+ throw new Error("branchName is required");
17472
+ const data = { branchName };
17473
+ if (baseBranch)
17474
+ data.baseBranch = baseBranch;
17475
+ if (snapshotId)
17476
+ data.snapshotID = snapshotId;
17477
+ return callDblabApi({
17478
+ apiKey,
17479
+ apiBaseUrl,
17480
+ instanceId,
17481
+ action: "/branch",
17482
+ method: "post",
17483
+ data,
17484
+ operation: "Failed to create branch",
17485
+ debug
17486
+ });
17487
+ }
17488
+ async function deleteBranch(params) {
17489
+ const { apiKey, apiBaseUrl, instanceId, branchName, debug } = params;
17490
+ if (!branchName)
17491
+ throw new Error("branchName is required");
17492
+ return callDblabApi({
17493
+ apiKey,
17494
+ apiBaseUrl,
17495
+ instanceId,
17496
+ action: `/branch/${encodeURIComponent(branchName)}`,
17497
+ method: "delete",
17498
+ operation: "Failed to delete branch",
17499
+ debug
17500
+ });
17501
+ }
17502
+ async function branchLog(params) {
17503
+ const { apiKey, apiBaseUrl, instanceId, branchName, debug } = params;
17504
+ if (!branchName)
17505
+ throw new Error("branchName is required");
17506
+ return callDblabApi({
17507
+ apiKey,
17508
+ apiBaseUrl,
17509
+ instanceId,
17510
+ action: `/branch/${encodeURIComponent(branchName)}/log`,
17511
+ method: "get",
17512
+ operation: "Failed to fetch branch log",
17513
+ debug
17514
+ });
17515
+ }
17516
+ async function listSnapshots(params) {
17517
+ const { apiKey, apiBaseUrl, instanceId, branchName, dataset, debug } = params;
17518
+ const qs = new URLSearchParams;
17519
+ const branch = branchName?.trim();
17520
+ if (branch)
17521
+ qs.append("branch", branch);
17522
+ if (dataset)
17523
+ qs.append("dataset", dataset);
17524
+ const action = `/snapshots${qs.toString() ? `?${qs.toString()}` : ""}`;
17525
+ return callDblabApi({
17526
+ apiKey,
17527
+ apiBaseUrl,
17528
+ instanceId,
17529
+ action,
17530
+ method: "get",
17531
+ operation: "Failed to list snapshots",
17532
+ debug
17533
+ });
17534
+ }
17535
+ async function createSnapshot(params) {
17536
+ const { apiKey, apiBaseUrl, instanceId, cloneId, message, debug } = params;
17537
+ if (!cloneId)
17538
+ throw new Error("cloneId is required");
17539
+ const data = { cloneID: cloneId };
17540
+ if (message)
17541
+ data.message = message;
17542
+ return callDblabApi({
17543
+ apiKey,
17544
+ apiBaseUrl,
17545
+ instanceId,
17546
+ action: "/branch/snapshot",
17547
+ method: "post",
17548
+ data,
17549
+ operation: "Failed to create snapshot",
17550
+ debug
17551
+ });
17552
+ }
17553
+ async function destroySnapshot(params) {
17554
+ const { apiKey, apiBaseUrl, instanceId, snapshotId, force, debug } = params;
17555
+ if (!snapshotId)
17556
+ throw new Error("snapshotId is required");
17557
+ const action = `/snapshot/${encodeURIComponent(snapshotId)}?force=${Boolean(force)}`;
17558
+ return callDblabApi({
17559
+ apiKey,
17560
+ apiBaseUrl,
17561
+ instanceId,
17562
+ action,
17563
+ method: "delete",
17564
+ operation: "Failed to destroy snapshot",
17565
+ debug
17566
+ });
17567
+ }
17568
+
17270
17569
  // lib/storage.ts
17271
17570
  import * as fs3 from "fs";
17272
17571
  import * as path3 from "path";
@@ -17488,6 +17787,319 @@ function appendAttachmentsToContent(content, attachments) {
17488
17787
  ${links}`;
17489
17788
  }
17490
17789
 
17790
+ // lib/joe.ts
17791
+ import * as fs4 from "fs";
17792
+ import * as path4 from "path";
17793
+ import * as crypto from "node:crypto";
17794
+ var JOE_COMMANDS = [
17795
+ "plan",
17796
+ "explain",
17797
+ "exec",
17798
+ "hypo",
17799
+ "activity",
17800
+ "terminate",
17801
+ "reset",
17802
+ "describe"
17803
+ ];
17804
+ var TERMINAL_STATES = new Set([
17805
+ "done",
17806
+ "error",
17807
+ "timed_out"
17808
+ ]);
17809
+ var AI_ENABLED_COMMANDS = new Set([
17810
+ "plan",
17811
+ "explain",
17812
+ "exec",
17813
+ "hypo",
17814
+ "activity",
17815
+ "describe"
17816
+ ]);
17817
+ var EXECUTING_COMMANDS = new Set([
17818
+ "exec",
17819
+ "explain"
17820
+ ]);
17821
+ var DEFAULT_BUDGET_MS = 25000;
17822
+ var DEFAULT_POLL_INTERVAL_MS = 800;
17823
+ async function callRpc(params) {
17824
+ const { apiKey, apiBaseUrl, fn, body, operation, debug } = params;
17825
+ if (!apiKey) {
17826
+ throw new Error("API key is required");
17827
+ }
17828
+ const base = normalizeBaseUrl(apiBaseUrl);
17829
+ const url = new URL(`${base}/rpc/${fn}`);
17830
+ const payload = JSON.stringify(body);
17831
+ const headers = {
17832
+ "access-token": apiKey,
17833
+ "Content-Type": "application/json",
17834
+ Connection: "close"
17835
+ };
17836
+ if (debug) {
17837
+ const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
17838
+ console.error(`Debug: POST URL: ${url.toString()}`);
17839
+ console.error(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
17840
+ console.error(`Debug: Request body: ${payload}`);
17841
+ }
17842
+ const response = await fetch(url.toString(), {
17843
+ method: "POST",
17844
+ headers,
17845
+ body: payload
17846
+ });
17847
+ const text = await response.text();
17848
+ if (debug) {
17849
+ console.error(`Debug: Response status: ${response.status}`);
17850
+ console.error(`Debug: Response body: ${text}`);
17851
+ }
17852
+ if (!response.ok) {
17853
+ throw new Error(formatHttpError(operation, response.status, text));
17854
+ }
17855
+ try {
17856
+ return JSON.parse(text);
17857
+ } catch {
17858
+ throw new Error(`${operation}: failed to parse response: ${text}`);
17859
+ }
17860
+ }
17861
+ async function submitCommand(params) {
17862
+ const { apiKey, apiBaseUrl, command, projectId, sql, args, sessionId, idempotencyKey, debug } = params;
17863
+ if (!JOE_COMMANDS.includes(command)) {
17864
+ throw new Error(`Unknown Joe command: ${command}`);
17865
+ }
17866
+ const body = {
17867
+ project_id: projectId,
17868
+ command,
17869
+ sql: sql ?? null,
17870
+ args: args ?? null,
17871
+ session_id: sessionId ?? null
17872
+ };
17873
+ if (idempotencyKey) {
17874
+ body.idempotency_key = idempotencyKey;
17875
+ }
17876
+ return callRpc({
17877
+ apiKey,
17878
+ apiBaseUrl,
17879
+ fn: "joe_command_submit",
17880
+ body,
17881
+ operation: `Failed to submit ${command} command`,
17882
+ debug
17883
+ });
17884
+ }
17885
+ async function getCommandStatus(params) {
17886
+ const { apiKey, apiBaseUrl, commandId, debug } = params;
17887
+ if (!commandId) {
17888
+ throw new Error("commandId is required");
17889
+ }
17890
+ return callRpc({
17891
+ apiKey,
17892
+ apiBaseUrl,
17893
+ fn: "joe_command_status",
17894
+ body: { command_id: commandId },
17895
+ operation: "Failed to fetch command status",
17896
+ debug
17897
+ });
17898
+ }
17899
+ async function getCommandResult(params) {
17900
+ const { apiKey, apiBaseUrl, commandId, debug } = params;
17901
+ if (!commandId) {
17902
+ throw new Error("commandId is required");
17903
+ }
17904
+ return callRpc({
17905
+ apiKey,
17906
+ apiBaseUrl,
17907
+ fn: "joe_command_result",
17908
+ body: { command_id: commandId },
17909
+ operation: "Failed to fetch command result",
17910
+ debug
17911
+ });
17912
+ }
17913
+ function normalizeProjectRow(row) {
17914
+ const projectId = row.project_id ?? row.id ?? 0;
17915
+ return {
17916
+ project_id: Number(projectId),
17917
+ alias: row.alias ?? null,
17918
+ name: row.name ?? null,
17919
+ label: row.label ?? null,
17920
+ joe_ready: Boolean(row.joe_ready ?? row.joe_api_v2_enabled ?? false),
17921
+ tunnel: Boolean(row.tunnel ?? row.tunnel_ready ?? false)
17922
+ };
17923
+ }
17924
+ async function listProjects(params) {
17925
+ const { apiKey, apiBaseUrl, orgId, debug } = params;
17926
+ const body = {};
17927
+ if (typeof orgId === "number") {
17928
+ body.org_id = orgId;
17929
+ }
17930
+ const rows = await callRpc({
17931
+ apiKey,
17932
+ apiBaseUrl,
17933
+ fn: "projects_list",
17934
+ body,
17935
+ operation: "Failed to list projects",
17936
+ debug
17937
+ });
17938
+ if (!Array.isArray(rows)) {
17939
+ return [];
17940
+ }
17941
+ return rows.map(normalizeProjectRow);
17942
+ }
17943
+ function isNumericProjectRef2(ref) {
17944
+ return /^[0-9]+$/.test(ref.trim());
17945
+ }
17946
+ async function resolveProjectId(params) {
17947
+ const ref = String(params.project ?? "").trim();
17948
+ if (!ref) {
17949
+ throw new Error("project is required (--project <id|alias>)");
17950
+ }
17951
+ if (isNumericProjectRef2(ref)) {
17952
+ return Number(ref);
17953
+ }
17954
+ const projects = await listProjects({
17955
+ apiKey: params.apiKey,
17956
+ apiBaseUrl: params.apiBaseUrl,
17957
+ orgId: params.orgId,
17958
+ debug: params.debug
17959
+ });
17960
+ const needle = ref.toLowerCase();
17961
+ const match = projects.find((p) => p.alias !== null && p.alias.toLowerCase() === needle || p.name !== null && p.name.toLowerCase() === needle);
17962
+ if (!match) {
17963
+ throw new Error(`Project not found for alias/name '${ref}'. Run 'pgai projects' to see available projects.`);
17964
+ }
17965
+ return match.project_id;
17966
+ }
17967
+ function sessionStorePath(dir) {
17968
+ return path4.join(dir ?? getConfigDir2(), "joe-sessions.json");
17969
+ }
17970
+ function readSessionStore(dir) {
17971
+ const file = sessionStorePath(dir);
17972
+ if (!fs4.existsSync(file)) {
17973
+ return {};
17974
+ }
17975
+ try {
17976
+ const parsed = JSON.parse(fs4.readFileSync(file, "utf8"));
17977
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
17978
+ return parsed;
17979
+ }
17980
+ } catch {}
17981
+ return {};
17982
+ }
17983
+ function readStoredSessionId(projectId, dir) {
17984
+ const store = readSessionStore(dir);
17985
+ const value = store[String(projectId)];
17986
+ return typeof value === "string" && value.length > 0 ? value : null;
17987
+ }
17988
+ function writeStoredSessionId(projectId, sessionId, dir) {
17989
+ const baseDir = dir ?? getConfigDir2();
17990
+ if (!fs4.existsSync(baseDir)) {
17991
+ fs4.mkdirSync(baseDir, { recursive: true, mode: 448 });
17992
+ }
17993
+ const store = readSessionStore(dir);
17994
+ store[String(projectId)] = sessionId;
17995
+ fs4.writeFileSync(sessionStorePath(dir), JSON.stringify(store, null, 2) + `
17996
+ `, { mode: 384 });
17997
+ }
17998
+ function clearStoredSessionId(projectId, dir) {
17999
+ const file = sessionStorePath(dir);
18000
+ if (!fs4.existsSync(file)) {
18001
+ return;
18002
+ }
18003
+ const store = readSessionStore(dir);
18004
+ if (String(projectId) in store) {
18005
+ delete store[String(projectId)];
18006
+ fs4.writeFileSync(file, JSON.stringify(store, null, 2) + `
18007
+ `, { mode: 384 });
18008
+ }
18009
+ }
18010
+ function computeIdempotencyKey(command, projectId, sql, args) {
18011
+ if (EXECUTING_COMMANDS.has(command)) {
18012
+ const canonical = `${projectId}
18013
+ ${command}
18014
+ ${sql ?? ""}
18015
+ ${JSON.stringify(args ?? null)}`;
18016
+ return `joe-${crypto.createHash("sha256").update(canonical).digest("hex")}`;
18017
+ }
18018
+ return `joe-${crypto.randomUUID()}`;
18019
+ }
18020
+ var defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
18021
+ async function runCommand(params) {
18022
+ const {
18023
+ apiKey,
18024
+ apiBaseUrl,
18025
+ command,
18026
+ projectId,
18027
+ sql,
18028
+ args,
18029
+ sessionId,
18030
+ debug
18031
+ } = params;
18032
+ const budgetMs = params.budgetMs ?? DEFAULT_BUDGET_MS;
18033
+ const pollIntervalMs = params.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
18034
+ const now = params.now ?? Date.now;
18035
+ const sleep = params.sleep ?? defaultSleep;
18036
+ const idempotencyKey = params.idempotencyKey ?? computeIdempotencyKey(command, projectId, sql, args);
18037
+ const submitted = await submitCommand({
18038
+ apiKey,
18039
+ apiBaseUrl,
18040
+ command,
18041
+ projectId,
18042
+ sql,
18043
+ args,
18044
+ sessionId,
18045
+ idempotencyKey,
18046
+ debug
18047
+ });
18048
+ const commandId = submitted.command_id;
18049
+ const newSessionId = submitted.session_id ?? sessionId ?? null;
18050
+ const deadline = now() + budgetMs;
18051
+ let status = submitted.status;
18052
+ while (true) {
18053
+ const statusResp = await getCommandStatus({ apiKey, apiBaseUrl, commandId, debug });
18054
+ status = statusResp.status;
18055
+ if (TERMINAL_STATES.has(status)) {
18056
+ break;
18057
+ }
18058
+ if (now() >= deadline) {
18059
+ return { commandId, sessionId: newSessionId, status, result: null, budgetExpired: true };
18060
+ }
18061
+ await sleep(pollIntervalMs);
18062
+ }
18063
+ const result = await getCommandResult({ apiKey, apiBaseUrl, commandId, debug });
18064
+ return { commandId, sessionId: newSessionId, status, result, budgetExpired: false };
18065
+ }
18066
+ async function executeJoeCommand(params) {
18067
+ const projectId = await resolveProjectId({
18068
+ apiKey: params.apiKey,
18069
+ apiBaseUrl: params.apiBaseUrl,
18070
+ project: params.project,
18071
+ orgId: params.orgId,
18072
+ debug: params.debug
18073
+ });
18074
+ let sessionId;
18075
+ if (params.newSession) {
18076
+ clearStoredSessionId(projectId, params.sessionDir);
18077
+ sessionId = null;
18078
+ } else if (params.session) {
18079
+ sessionId = String(params.session);
18080
+ } else {
18081
+ sessionId = readStoredSessionId(projectId, params.sessionDir);
18082
+ }
18083
+ const outcome = await runCommand({
18084
+ apiKey: params.apiKey,
18085
+ apiBaseUrl: params.apiBaseUrl,
18086
+ command: params.command,
18087
+ projectId,
18088
+ sql: params.sql,
18089
+ args: params.args,
18090
+ sessionId,
18091
+ budgetMs: params.budgetMs,
18092
+ pollIntervalMs: params.pollIntervalMs,
18093
+ debug: params.debug,
18094
+ now: params.now,
18095
+ sleep: params.sleep
18096
+ });
18097
+ if (outcome.sessionId) {
18098
+ writeStoredSessionId(projectId, outcome.sessionId, params.sessionDir);
18099
+ }
18100
+ return { ...outcome, command: params.command, projectId };
18101
+ }
18102
+
17491
18103
  // node_modules/zod/v4/core/core.js
17492
18104
  var NEVER = Object.freeze({
17493
18105
  status: "aborted"
@@ -17733,10 +18345,10 @@ function mergeDefs(...defs) {
17733
18345
  function cloneDef(schema2) {
17734
18346
  return mergeDefs(schema2._zod.def);
17735
18347
  }
17736
- function getElementAtPath(obj, path4) {
17737
- if (!path4)
18348
+ function getElementAtPath(obj, path5) {
18349
+ if (!path5)
17738
18350
  return obj;
17739
- return path4.reduce((acc, key) => acc?.[key], obj);
18351
+ return path5.reduce((acc, key) => acc?.[key], obj);
17740
18352
  }
17741
18353
  function promiseAllObject(promisesObj) {
17742
18354
  const keys = Object.keys(promisesObj);
@@ -18100,11 +18712,11 @@ function aborted(x, startIndex = 0) {
18100
18712
  }
18101
18713
  return false;
18102
18714
  }
18103
- function prefixIssues(path4, issues) {
18715
+ function prefixIssues(path5, issues) {
18104
18716
  return issues.map((iss) => {
18105
18717
  var _a;
18106
18718
  (_a = iss).path ?? (_a.path = []);
18107
- iss.path.unshift(path4);
18719
+ iss.path.unshift(path5);
18108
18720
  return iss;
18109
18721
  });
18110
18722
  }
@@ -24376,44 +24988,238 @@ class StdioServerTransport {
24376
24988
  // lib/mcp-server.ts
24377
24989
  var interpretEscapes = (str2) => (str2 || "").replace(/\\n/g, `
24378
24990
  `).replace(/\\t/g, "\t").replace(/\\r/g, "\r").replace(/\\"/g, '"').replace(/\\'/g, "'");
24379
- async function handleToolCall(req, rootOpts, extra) {
24380
- const toolName = req.params.name;
24381
- const args = req.params.arguments || {};
24382
- const cfg = readConfig2();
24383
- const apiKey = (rootOpts?.apiKey || process.env.PGAI_API_KEY || cfg.apiKey || "").toString();
24384
- const { apiBaseUrl } = resolveBaseUrls(rootOpts, cfg);
24385
- const debug = Boolean(args.debug ?? extra?.debug);
24386
- if (!apiKey) {
24387
- return {
24388
- content: [
24389
- {
24390
- type: "text",
24391
- text: "API key is required. Run 'pgai auth' or set PGAI_API_KEY."
24392
- }
24393
- ],
24394
- isError: true
24395
- };
24396
- }
24397
- try {
24398
- if (toolName === "list_issues") {
24399
- const orgId = args.org_id !== undefined ? Number(args.org_id) : cfg.orgId ?? undefined;
24400
- const statusArg = args.status ? String(args.status) : undefined;
24401
- let status;
24402
- if (statusArg === "open")
24403
- status = "open";
24404
- else if (statusArg === "closed")
24405
- status = "closed";
24406
- const limit = args.limit !== undefined ? Number(args.limit) : undefined;
24407
- const offset = args.offset !== undefined ? Number(args.offset) : undefined;
24408
- const issues = await fetchIssues({ apiKey, apiBaseUrl, orgId, status, limit, offset, debug });
24409
- return { content: [{ type: "text", text: JSON.stringify(issues, null, 2) }] };
24410
- }
24411
- if (toolName === "view_issue") {
24412
- const issueId = String(args.issue_id || "").trim();
24413
- if (!issueId) {
24414
- return { content: [{ type: "text", text: "issue_id is required" }], isError: true };
24991
+ var JOE_TOOL_TO_COMMAND = {
24992
+ plan_query: "plan",
24993
+ explain_query: "explain",
24994
+ exec_sql: "exec",
24995
+ hypo_index: "hypo",
24996
+ activity: "activity",
24997
+ terminate_backend: "terminate",
24998
+ reset_clone: "reset",
24999
+ describe: "describe"
25000
+ };
25001
+ function joeToolDefinitions() {
25002
+ const sessionProps = {
25003
+ project_id: {
25004
+ type: "string",
25005
+ description: "Target project by numeric id OR alias/name (id-or-alias). --project 12 ≡ main-db."
25006
+ },
25007
+ session_id: {
25008
+ type: "string",
25009
+ description: "Run on a specific session's clone (64-bit id as a string). Auto-reused per project when omitted."
25010
+ },
25011
+ new_session: { type: "boolean", description: "Force a fresh session/clone (null session)." },
25012
+ timeout_ms: { type: "number", description: "One-shot poll budget in ms (≤ 25000); on expiry returns a command_id to resume." },
25013
+ debug: { type: "boolean", description: "Enable verbose debug logs." }
25014
+ };
25015
+ return [
25016
+ {
25017
+ name: "plan_query",
25018
+ 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.",
25019
+ inputSchema: {
25020
+ type: "object",
25021
+ properties: { sql: { type: "string", description: "The query to plan (one explainable statement)." }, ...sessionProps },
25022
+ required: ["sql", "project_id"],
25023
+ additionalProperties: false
24415
25024
  }
24416
- const issue2 = await fetchIssue({ apiKey, apiBaseUrl, issueId, debug });
25025
+ },
25026
+ {
25027
+ name: "explain_query",
25028
+ 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).",
25029
+ inputSchema: {
25030
+ type: "object",
25031
+ properties: { sql: { type: "string", description: "The query to EXPLAIN ANALYZE (executes on the clone)." }, ...sessionProps },
25032
+ required: ["sql", "project_id"],
25033
+ additionalProperties: false
25034
+ }
25035
+ },
25036
+ {
25037
+ name: "exec_sql",
25038
+ 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.",
25039
+ inputSchema: {
25040
+ type: "object",
25041
+ properties: { sql: { type: "string", description: "Arbitrary DDL/DML to run on the clone (e.g. create index, analyze)." }, ...sessionProps },
25042
+ required: ["sql", "project_id"],
25043
+ additionalProperties: false
25044
+ }
25045
+ },
25046
+ {
25047
+ name: "hypo_index",
25048
+ description: "HypoPG hypothetical index + re-plan (no real index built, no data change) — would the planner switch to it? LLM-facing. Requires joe:plan.",
25049
+ inputSchema: {
25050
+ type: "object",
25051
+ properties: {
25052
+ sql: { type: "string", description: "Candidate index DDL (e.g. create index on orders (customer_id))." },
25053
+ query: { type: "string", description: "Target query the hypothetical index is evaluated against." },
25054
+ ...sessionProps
25055
+ },
25056
+ required: ["sql", "query", "project_id"],
25057
+ additionalProperties: false
25058
+ }
25059
+ },
25060
+ {
25061
+ name: "activity",
25062
+ description: "pg_stat_activity snapshot on the clone (query text redacted). LLM-facing. Requires joe:plan.",
25063
+ inputSchema: { type: "object", properties: { ...sessionProps }, required: ["project_id"], additionalProperties: false }
25064
+ },
25065
+ {
25066
+ name: "terminate_backend",
25067
+ description: "pg_terminate_backend(pid) on the clone. Status-only (no data to the LLM). Requires joe:admin.",
25068
+ inputSchema: {
25069
+ type: "object",
25070
+ properties: { pid: { type: "number", description: "Backend pid to terminate." }, ...sessionProps },
25071
+ required: ["pid", "project_id"],
25072
+ additionalProperties: false
25073
+ }
25074
+ },
25075
+ {
25076
+ name: "reset_clone",
25077
+ description: "Reset/recreate the session's thin clone. Status-only (no data to the LLM). Requires joe:admin.",
25078
+ inputSchema: { type: "object", properties: { ...sessionProps }, required: ["project_id"], additionalProperties: false }
25079
+ },
25080
+ {
25081
+ name: "describe",
25082
+ description: "\\d-family schema/relation/index/db/view metadata introspection. LLM-facing (metadata). Requires joe:plan.",
25083
+ inputSchema: {
25084
+ type: "object",
25085
+ properties: {
25086
+ object: { type: "string", description: "Object to describe (e.g. users)." },
25087
+ variant: { type: "string", description: "\\d-family variant (e.g. \\d+, \\di, \\dt)." },
25088
+ ...sessionProps
25089
+ },
25090
+ required: ["object", "project_id"],
25091
+ additionalProperties: false
25092
+ }
25093
+ },
25094
+ {
25095
+ name: "list_projects",
25096
+ 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.",
25097
+ inputSchema: {
25098
+ type: "object",
25099
+ properties: {
25100
+ org_id: { type: "number", description: "Organization ID (optional, falls back to config)." },
25101
+ debug: { type: "boolean", description: "Enable verbose debug logs." }
25102
+ },
25103
+ additionalProperties: false
25104
+ }
25105
+ }
25106
+ ];
25107
+ }
25108
+ async function runJoeTool(params) {
25109
+ const command = JOE_TOOL_TO_COMMAND[params.toolName];
25110
+ const a = params.args;
25111
+ const project = String(a.project_id ?? "").trim();
25112
+ if (!project) {
25113
+ return { content: [{ type: "text", text: "project_id is required" }], isError: true };
25114
+ }
25115
+ let sql = null;
25116
+ let cmdArgs = null;
25117
+ if (command === "plan" || command === "explain" || command === "exec" || command === "hypo") {
25118
+ sql = String(a.sql ?? "").trim();
25119
+ if (!sql) {
25120
+ return { content: [{ type: "text", text: "sql is required" }], isError: true };
25121
+ }
25122
+ }
25123
+ if (command === "hypo") {
25124
+ const query = String(a.query ?? "").trim();
25125
+ if (!query) {
25126
+ return { content: [{ type: "text", text: "query is required for hypo_index" }], isError: true };
25127
+ }
25128
+ cmdArgs = { query };
25129
+ }
25130
+ if (command === "terminate") {
25131
+ const pid = Number(a.pid);
25132
+ if (!Number.isFinite(pid)) {
25133
+ return { content: [{ type: "text", text: "pid (number) is required for terminate_backend" }], isError: true };
25134
+ }
25135
+ cmdArgs = { pid };
25136
+ }
25137
+ if (command === "describe") {
25138
+ const object4 = String(a.object ?? "").trim();
25139
+ if (!object4) {
25140
+ return { content: [{ type: "text", text: "object is required for describe" }], isError: true };
25141
+ }
25142
+ cmdArgs = { object: object4 };
25143
+ if (a.variant !== undefined)
25144
+ cmdArgs.variant = String(a.variant);
25145
+ }
25146
+ const session = a.session_id !== undefined && a.session_id !== null ? String(a.session_id) : null;
25147
+ const newSession = a.new_session === true;
25148
+ const budgetMs = a.timeout_ms !== undefined ? Number(a.timeout_ms) : undefined;
25149
+ const outcome = await executeJoeCommand({
25150
+ apiKey: params.apiKey,
25151
+ apiBaseUrl: params.apiBaseUrl,
25152
+ command,
25153
+ project,
25154
+ sql,
25155
+ args: cmdArgs,
25156
+ session,
25157
+ newSession,
25158
+ orgId: params.orgId,
25159
+ budgetMs,
25160
+ debug: params.debug
25161
+ });
25162
+ const payload = {
25163
+ command,
25164
+ command_id: outcome.commandId,
25165
+ session_id: outcome.sessionId,
25166
+ status: outcome.status,
25167
+ budget_expired: outcome.budgetExpired,
25168
+ resume: outcome.budgetExpired ? `pgai result ${outcome.commandId}` : undefined,
25169
+ result: outcome.result
25170
+ };
25171
+ const isError = outcome.status === "error" || outcome.status === "timed_out";
25172
+ return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }], isError };
25173
+ }
25174
+ async function resolveDblabInstanceForTool(apiKey, apiBaseUrl, args, cfgOrgId, debug) {
25175
+ const project = String(args.project || "").trim();
25176
+ if (!project) {
25177
+ throw new Error("project is required (project id or alias)");
25178
+ }
25179
+ const orgId = args.org_id !== undefined ? Number(args.org_id) : cfgOrgId ?? undefined;
25180
+ return resolveDblabInstanceId({ apiKey, apiBaseUrl, project, orgId, debug });
25181
+ }
25182
+ function optStr(v) {
25183
+ return v !== undefined && v !== null && String(v).length > 0 ? String(v) : undefined;
25184
+ }
25185
+ async function handleToolCall(req, rootOpts, extra) {
25186
+ const toolName = req.params.name;
25187
+ const args = req.params.arguments || {};
25188
+ const cfg = readConfig2();
25189
+ const apiKey = (rootOpts?.apiKey || process.env.PGAI_API_KEY || cfg.apiKey || "").toString();
25190
+ const { apiBaseUrl } = resolveBaseUrls(rootOpts, cfg);
25191
+ const debug = Boolean(args.debug ?? extra?.debug);
25192
+ if (!apiKey) {
25193
+ return {
25194
+ content: [
25195
+ {
25196
+ type: "text",
25197
+ text: "API key is required. Run 'pgai auth' or set PGAI_API_KEY."
25198
+ }
25199
+ ],
25200
+ isError: true
25201
+ };
25202
+ }
25203
+ try {
25204
+ if (toolName === "list_issues") {
25205
+ const orgId = args.org_id !== undefined ? Number(args.org_id) : cfg.orgId ?? undefined;
25206
+ const statusArg = args.status ? String(args.status) : undefined;
25207
+ let status;
25208
+ if (statusArg === "open")
25209
+ status = "open";
25210
+ else if (statusArg === "closed")
25211
+ status = "closed";
25212
+ const limit = args.limit !== undefined ? Number(args.limit) : undefined;
25213
+ const offset = args.offset !== undefined ? Number(args.offset) : undefined;
25214
+ const issues = await fetchIssues({ apiKey, apiBaseUrl, orgId, status, limit, offset, debug });
25215
+ return { content: [{ type: "text", text: JSON.stringify(issues, null, 2) }] };
25216
+ }
25217
+ if (toolName === "view_issue") {
25218
+ const issueId = String(args.issue_id || "").trim();
25219
+ if (!issueId) {
25220
+ return { content: [{ type: "text", text: "issue_id is required" }], isError: true };
25221
+ }
25222
+ const issue2 = await fetchIssue({ apiKey, apiBaseUrl, issueId, debug });
24417
25223
  if (!issue2) {
24418
25224
  return { content: [{ type: "text", text: "Issue not found" }], isError: true };
24419
25225
  }
@@ -24653,6 +25459,147 @@ async function handleToolCall(req, rootOpts, extra) {
24653
25459
  const files = await fetchReportFileData({ apiKey, apiBaseUrl, reportId, type: type2, checkId, debug });
24654
25460
  return { content: [{ type: "text", text: JSON.stringify(files, null, 2) }] };
24655
25461
  }
25462
+ if (toolName === "list_projects") {
25463
+ const orgId = args.org_id !== undefined ? Number(args.org_id) : cfg.orgId ?? undefined;
25464
+ const projects = await listProjects({ apiKey, apiBaseUrl, orgId, debug });
25465
+ return { content: [{ type: "text", text: JSON.stringify(projects, null, 2) }] };
25466
+ }
25467
+ if (toolName in JOE_TOOL_TO_COMMAND) {
25468
+ const orgId = cfg.orgId ?? undefined;
25469
+ return await runJoeTool({ toolName, apiKey, apiBaseUrl, orgId, args, debug });
25470
+ }
25471
+ if (toolName === "clone_create") {
25472
+ const instanceId = await resolveDblabInstanceForTool(apiKey, apiBaseUrl, args, cfg.orgId, debug);
25473
+ const result = await createClone({
25474
+ apiKey,
25475
+ apiBaseUrl,
25476
+ instanceId,
25477
+ cloneId: optStr(args.clone_id),
25478
+ branch: optStr(args.branch),
25479
+ snapshotId: optStr(args.snapshot_id),
25480
+ dbUser: optStr(args.db_user),
25481
+ dbPassword: optStr(args.db_password),
25482
+ isProtected: args.protected === true,
25483
+ debug
25484
+ });
25485
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
25486
+ }
25487
+ if (toolName === "clone_list") {
25488
+ const instanceId = await resolveDblabInstanceForTool(apiKey, apiBaseUrl, args, cfg.orgId, debug);
25489
+ const result = await listClones({ apiKey, apiBaseUrl, instanceId, debug });
25490
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
25491
+ }
25492
+ if (toolName === "clone_status") {
25493
+ const cloneId = String(args.clone_id || "").trim();
25494
+ if (!cloneId)
25495
+ return { content: [{ type: "text", text: "clone_id is required" }], isError: true };
25496
+ const instanceId = await resolveDblabInstanceForTool(apiKey, apiBaseUrl, args, cfg.orgId, debug);
25497
+ const result = await getClone({ apiKey, apiBaseUrl, instanceId, cloneId, debug });
25498
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
25499
+ }
25500
+ if (toolName === "clone_reset") {
25501
+ const cloneId = String(args.clone_id || "").trim();
25502
+ if (!cloneId)
25503
+ return { content: [{ type: "text", text: "clone_id is required" }], isError: true };
25504
+ const instanceId = await resolveDblabInstanceForTool(apiKey, apiBaseUrl, args, cfg.orgId, debug);
25505
+ const result = await resetClone({
25506
+ apiKey,
25507
+ apiBaseUrl,
25508
+ instanceId,
25509
+ cloneId,
25510
+ snapshotId: optStr(args.snapshot_id),
25511
+ latest: args.latest === true ? true : undefined,
25512
+ debug
25513
+ });
25514
+ return { content: [{ type: "text", text: JSON.stringify(result ?? { reset: true, cloneId }, null, 2) }] };
25515
+ }
25516
+ if (toolName === "clone_destroy") {
25517
+ const cloneId = String(args.clone_id || "").trim();
25518
+ if (!cloneId)
25519
+ return { content: [{ type: "text", text: "clone_id is required" }], isError: true };
25520
+ const instanceId = await resolveDblabInstanceForTool(apiKey, apiBaseUrl, args, cfg.orgId, debug);
25521
+ const result = await destroyClone({ apiKey, apiBaseUrl, instanceId, cloneId, debug });
25522
+ return { content: [{ type: "text", text: JSON.stringify(result ?? { destroyed: true, cloneId }, null, 2) }] };
25523
+ }
25524
+ if (toolName === "branch_list") {
25525
+ const instanceId = await resolveDblabInstanceForTool(apiKey, apiBaseUrl, args, cfg.orgId, debug);
25526
+ const result = await listBranches({ apiKey, apiBaseUrl, instanceId, debug });
25527
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
25528
+ }
25529
+ if (toolName === "branch_create") {
25530
+ const branchName = String(args.name || "").trim();
25531
+ if (!branchName)
25532
+ return { content: [{ type: "text", text: "name is required" }], isError: true };
25533
+ const instanceId = await resolveDblabInstanceForTool(apiKey, apiBaseUrl, args, cfg.orgId, debug);
25534
+ const result = await createBranch({
25535
+ apiKey,
25536
+ apiBaseUrl,
25537
+ instanceId,
25538
+ branchName,
25539
+ baseBranch: optStr(args.base_branch),
25540
+ snapshotId: optStr(args.snapshot_id),
25541
+ debug
25542
+ });
25543
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
25544
+ }
25545
+ if (toolName === "branch_delete") {
25546
+ const branchName = String(args.name || "").trim();
25547
+ if (!branchName)
25548
+ return { content: [{ type: "text", text: "name is required" }], isError: true };
25549
+ const instanceId = await resolveDblabInstanceForTool(apiKey, apiBaseUrl, args, cfg.orgId, debug);
25550
+ const result = await deleteBranch({ apiKey, apiBaseUrl, instanceId, branchName, debug });
25551
+ return { content: [{ type: "text", text: JSON.stringify(result ?? { deleted: true, branch: branchName }, null, 2) }] };
25552
+ }
25553
+ if (toolName === "branch_log") {
25554
+ const branchName = String(args.name || "").trim();
25555
+ if (!branchName)
25556
+ return { content: [{ type: "text", text: "name is required" }], isError: true };
25557
+ const instanceId = await resolveDblabInstanceForTool(apiKey, apiBaseUrl, args, cfg.orgId, debug);
25558
+ const result = await branchLog({ apiKey, apiBaseUrl, instanceId, branchName, debug });
25559
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
25560
+ }
25561
+ if (toolName === "snapshot_list") {
25562
+ const instanceId = await resolveDblabInstanceForTool(apiKey, apiBaseUrl, args, cfg.orgId, debug);
25563
+ const result = await listSnapshots({
25564
+ apiKey,
25565
+ apiBaseUrl,
25566
+ instanceId,
25567
+ branchName: optStr(args.branch),
25568
+ dataset: optStr(args.dataset),
25569
+ debug
25570
+ });
25571
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
25572
+ }
25573
+ if (toolName === "snapshot_create") {
25574
+ const cloneId = String(args.clone_id || "").trim();
25575
+ if (!cloneId)
25576
+ return { content: [{ type: "text", text: "clone_id is required" }], isError: true };
25577
+ const instanceId = await resolveDblabInstanceForTool(apiKey, apiBaseUrl, args, cfg.orgId, debug);
25578
+ const result = await createSnapshot({
25579
+ apiKey,
25580
+ apiBaseUrl,
25581
+ instanceId,
25582
+ cloneId,
25583
+ message: optStr(args.message),
25584
+ debug
25585
+ });
25586
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
25587
+ }
25588
+ if (toolName === "snapshot_destroy") {
25589
+ const snapshotId = String(args.snapshot_id || "").trim();
25590
+ if (!snapshotId)
25591
+ return { content: [{ type: "text", text: "snapshot_id is required" }], isError: true };
25592
+ const instanceId = await resolveDblabInstanceForTool(apiKey, apiBaseUrl, args, cfg.orgId, debug);
25593
+ const result = await destroySnapshot({
25594
+ apiKey,
25595
+ apiBaseUrl,
25596
+ instanceId,
25597
+ snapshotId,
25598
+ force: args.force === true,
25599
+ debug
25600
+ });
25601
+ return { content: [{ type: "text", text: JSON.stringify(result ?? { destroyed: true, snapshot: snapshotId }, null, 2) }] };
25602
+ }
24656
25603
  throw new Error(`Unknown tool: ${toolName}`);
24657
25604
  } catch (err) {
24658
25605
  const message = err instanceof Error ? err.message : String(err);
@@ -24929,6 +25876,197 @@ async function startMcpServer(rootOpts, extra) {
24929
25876
  },
24930
25877
  additionalProperties: false
24931
25878
  }
25879
+ },
25880
+ ...joeToolDefinitions(),
25881
+ {
25882
+ name: "clone_create",
25883
+ description: "Create a DBLab thin clone of the project's database (same as CLI 'clone create'). Requires the joe:plan scope.",
25884
+ inputSchema: {
25885
+ type: "object",
25886
+ properties: {
25887
+ project: { type: "string", description: "Project id or alias" },
25888
+ branch: { type: "string", description: "Branch to clone from" },
25889
+ snapshot_id: { type: "string", description: "Snapshot id to clone from" },
25890
+ clone_id: { type: "string", description: "Clone id (DBLab generates one when omitted)" },
25891
+ db_user: { type: "string", description: "Clone DB user" },
25892
+ db_password: { type: "string", description: "Clone DB password" },
25893
+ protected: { type: "boolean", description: "Protect the clone from auto-deletion" },
25894
+ org_id: { type: "number", description: "Organization id (optional, falls back to config)" },
25895
+ debug: { type: "boolean", description: "Enable verbose debug logs" }
25896
+ },
25897
+ required: ["project"],
25898
+ additionalProperties: false
25899
+ }
25900
+ },
25901
+ {
25902
+ name: "clone_list",
25903
+ description: "List the project's DBLab thin clones (same as CLI 'clone list').",
25904
+ inputSchema: {
25905
+ type: "object",
25906
+ properties: {
25907
+ project: { type: "string", description: "Project id or alias" },
25908
+ org_id: { type: "number", description: "Organization id (optional, falls back to config)" },
25909
+ debug: { type: "boolean", description: "Enable verbose debug logs" }
25910
+ },
25911
+ required: ["project"],
25912
+ additionalProperties: false
25913
+ }
25914
+ },
25915
+ {
25916
+ name: "clone_status",
25917
+ description: "Show a DBLab clone's status (same as CLI 'clone status').",
25918
+ inputSchema: {
25919
+ type: "object",
25920
+ properties: {
25921
+ project: { type: "string", description: "Project id or alias" },
25922
+ clone_id: { type: "string", description: "Clone id" },
25923
+ org_id: { type: "number", description: "Organization id (optional, falls back to config)" },
25924
+ debug: { type: "boolean", description: "Enable verbose debug logs" }
25925
+ },
25926
+ required: ["project", "clone_id"],
25927
+ additionalProperties: false
25928
+ }
25929
+ },
25930
+ {
25931
+ name: "clone_reset",
25932
+ description: "Reset a DBLab clone to a pristine snapshot (same as CLI 'clone reset'). Requires the joe:admin scope.",
25933
+ inputSchema: {
25934
+ type: "object",
25935
+ properties: {
25936
+ project: { type: "string", description: "Project id or alias" },
25937
+ clone_id: { type: "string", description: "Clone id" },
25938
+ snapshot_id: { type: "string", description: "Snapshot id to reset to (default: latest)" },
25939
+ latest: { type: "boolean", description: "Reset to the latest snapshot" },
25940
+ org_id: { type: "number", description: "Organization id (optional, falls back to config)" },
25941
+ debug: { type: "boolean", description: "Enable verbose debug logs" }
25942
+ },
25943
+ required: ["project", "clone_id"],
25944
+ additionalProperties: false
25945
+ }
25946
+ },
25947
+ {
25948
+ name: "clone_destroy",
25949
+ description: "Destroy a DBLab clone (same as CLI 'clone destroy'). Requires the joe:admin scope.",
25950
+ inputSchema: {
25951
+ type: "object",
25952
+ properties: {
25953
+ project: { type: "string", description: "Project id or alias" },
25954
+ clone_id: { type: "string", description: "Clone id" },
25955
+ org_id: { type: "number", description: "Organization id (optional, falls back to config)" },
25956
+ debug: { type: "boolean", description: "Enable verbose debug logs" }
25957
+ },
25958
+ required: ["project", "clone_id"],
25959
+ additionalProperties: false
25960
+ }
25961
+ },
25962
+ {
25963
+ name: "branch_list",
25964
+ description: "List the project's DBLab branches (same as CLI 'branch list').",
25965
+ inputSchema: {
25966
+ type: "object",
25967
+ properties: {
25968
+ project: { type: "string", description: "Project id or alias" },
25969
+ org_id: { type: "number", description: "Organization id (optional, falls back to config)" },
25970
+ debug: { type: "boolean", description: "Enable verbose debug logs" }
25971
+ },
25972
+ required: ["project"],
25973
+ additionalProperties: false
25974
+ }
25975
+ },
25976
+ {
25977
+ name: "branch_create",
25978
+ description: "Create a DBLab branch (same as CLI 'branch create'). Requires the joe:plan scope.",
25979
+ inputSchema: {
25980
+ type: "object",
25981
+ properties: {
25982
+ project: { type: "string", description: "Project id or alias" },
25983
+ name: { type: "string", description: "Branch name" },
25984
+ snapshot_id: { type: "string", description: "Snapshot id to base the branch on" },
25985
+ base_branch: { type: "string", description: "Parent branch to fork from" },
25986
+ org_id: { type: "number", description: "Organization id (optional, falls back to config)" },
25987
+ debug: { type: "boolean", description: "Enable verbose debug logs" }
25988
+ },
25989
+ required: ["project", "name"],
25990
+ additionalProperties: false
25991
+ }
25992
+ },
25993
+ {
25994
+ name: "branch_delete",
25995
+ description: "Delete a DBLab branch (same as CLI 'branch delete'). Requires the joe:admin scope.",
25996
+ inputSchema: {
25997
+ type: "object",
25998
+ properties: {
25999
+ project: { type: "string", description: "Project id or alias" },
26000
+ name: { type: "string", description: "Branch name" },
26001
+ org_id: { type: "number", description: "Organization id (optional, falls back to config)" },
26002
+ debug: { type: "boolean", description: "Enable verbose debug logs" }
26003
+ },
26004
+ required: ["project", "name"],
26005
+ additionalProperties: false
26006
+ }
26007
+ },
26008
+ {
26009
+ name: "branch_log",
26010
+ description: "Show a DBLab branch's snapshot log (same as CLI 'branch log').",
26011
+ inputSchema: {
26012
+ type: "object",
26013
+ properties: {
26014
+ project: { type: "string", description: "Project id or alias" },
26015
+ name: { type: "string", description: "Branch name" },
26016
+ org_id: { type: "number", description: "Organization id (optional, falls back to config)" },
26017
+ debug: { type: "boolean", description: "Enable verbose debug logs" }
26018
+ },
26019
+ required: ["project", "name"],
26020
+ additionalProperties: false
26021
+ }
26022
+ },
26023
+ {
26024
+ name: "snapshot_list",
26025
+ description: "List the project's DBLab snapshots (same as CLI 'snapshot list').",
26026
+ inputSchema: {
26027
+ type: "object",
26028
+ properties: {
26029
+ project: { type: "string", description: "Project id or alias" },
26030
+ branch: { type: "string", description: "Filter by branch" },
26031
+ dataset: { type: "string", description: "Filter by dataset" },
26032
+ org_id: { type: "number", description: "Organization id (optional, falls back to config)" },
26033
+ debug: { type: "boolean", description: "Enable verbose debug logs" }
26034
+ },
26035
+ required: ["project"],
26036
+ additionalProperties: false
26037
+ }
26038
+ },
26039
+ {
26040
+ name: "snapshot_create",
26041
+ description: "Create a DBLab snapshot from a clone (same as CLI 'snapshot create'). Requires the joe:plan scope.",
26042
+ inputSchema: {
26043
+ type: "object",
26044
+ properties: {
26045
+ project: { type: "string", description: "Project id or alias" },
26046
+ clone_id: { type: "string", description: "Clone id to snapshot" },
26047
+ message: { type: "string", description: "Snapshot message" },
26048
+ org_id: { type: "number", description: "Organization id (optional, falls back to config)" },
26049
+ debug: { type: "boolean", description: "Enable verbose debug logs" }
26050
+ },
26051
+ required: ["project", "clone_id"],
26052
+ additionalProperties: false
26053
+ }
26054
+ },
26055
+ {
26056
+ name: "snapshot_destroy",
26057
+ description: "Destroy a DBLab snapshot (same as CLI 'snapshot destroy'). Requires the joe:admin scope.",
26058
+ inputSchema: {
26059
+ type: "object",
26060
+ properties: {
26061
+ project: { type: "string", description: "Project id or alias" },
26062
+ snapshot_id: { type: "string", description: "Snapshot id" },
26063
+ force: { type: "boolean", description: "Force-delete even when dependent clones exist" },
26064
+ org_id: { type: "number", description: "Organization id (optional, falls back to config)" },
26065
+ debug: { type: "boolean", description: "Enable verbose debug logs" }
26066
+ },
26067
+ required: ["project", "snapshot_id"],
26068
+ additionalProperties: false
26069
+ }
24932
26070
  }
24933
26071
  ]
24934
26072
  };
@@ -25801,11 +26939,709 @@ function renderMarkdownForTerminal(md) {
25801
26939
  `);
25802
26940
  }
25803
26941
 
26942
+ // lib/joe.ts
26943
+ import * as fs5 from "fs";
26944
+ import * as path5 from "path";
26945
+ import * as crypto2 from "node:crypto";
26946
+ var JOE_COMMANDS2 = [
26947
+ "plan",
26948
+ "explain",
26949
+ "exec",
26950
+ "hypo",
26951
+ "activity",
26952
+ "terminate",
26953
+ "reset",
26954
+ "describe"
26955
+ ];
26956
+ var TERMINAL_STATES2 = new Set([
26957
+ "done",
26958
+ "error",
26959
+ "timed_out"
26960
+ ]);
26961
+ var AI_ENABLED_COMMANDS2 = new Set([
26962
+ "plan",
26963
+ "explain",
26964
+ "exec",
26965
+ "hypo",
26966
+ "activity",
26967
+ "describe"
26968
+ ]);
26969
+ var EXECUTING_COMMANDS2 = new Set([
26970
+ "exec",
26971
+ "explain"
26972
+ ]);
26973
+ var DEFAULT_BUDGET_MS2 = 25000;
26974
+ var DEFAULT_POLL_INTERVAL_MS2 = 800;
26975
+ async function callRpc2(params) {
26976
+ const { apiKey, apiBaseUrl, fn, body, operation, debug } = params;
26977
+ if (!apiKey) {
26978
+ throw new Error("API key is required");
26979
+ }
26980
+ const base = normalizeBaseUrl(apiBaseUrl);
26981
+ const url = new URL(`${base}/rpc/${fn}`);
26982
+ const payload = JSON.stringify(body);
26983
+ const headers = {
26984
+ "access-token": apiKey,
26985
+ "Content-Type": "application/json",
26986
+ Connection: "close"
26987
+ };
26988
+ if (debug) {
26989
+ const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
26990
+ console.error(`Debug: POST URL: ${url.toString()}`);
26991
+ console.error(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
26992
+ console.error(`Debug: Request body: ${payload}`);
26993
+ }
26994
+ const response = await fetch(url.toString(), {
26995
+ method: "POST",
26996
+ headers,
26997
+ body: payload
26998
+ });
26999
+ const text = await response.text();
27000
+ if (debug) {
27001
+ console.error(`Debug: Response status: ${response.status}`);
27002
+ console.error(`Debug: Response body: ${text}`);
27003
+ }
27004
+ if (!response.ok) {
27005
+ throw new Error(formatHttpError(operation, response.status, text));
27006
+ }
27007
+ try {
27008
+ return JSON.parse(text);
27009
+ } catch {
27010
+ throw new Error(`${operation}: failed to parse response: ${text}`);
27011
+ }
27012
+ }
27013
+ async function submitCommand2(params) {
27014
+ const { apiKey, apiBaseUrl, command, projectId, sql, args, sessionId, idempotencyKey, debug } = params;
27015
+ if (!JOE_COMMANDS2.includes(command)) {
27016
+ throw new Error(`Unknown Joe command: ${command}`);
27017
+ }
27018
+ const body = {
27019
+ project_id: projectId,
27020
+ command,
27021
+ sql: sql ?? null,
27022
+ args: args ?? null,
27023
+ session_id: sessionId ?? null
27024
+ };
27025
+ if (idempotencyKey) {
27026
+ body.idempotency_key = idempotencyKey;
27027
+ }
27028
+ return callRpc2({
27029
+ apiKey,
27030
+ apiBaseUrl,
27031
+ fn: "joe_command_submit",
27032
+ body,
27033
+ operation: `Failed to submit ${command} command`,
27034
+ debug
27035
+ });
27036
+ }
27037
+ async function getCommandStatus2(params) {
27038
+ const { apiKey, apiBaseUrl, commandId, debug } = params;
27039
+ if (!commandId) {
27040
+ throw new Error("commandId is required");
27041
+ }
27042
+ return callRpc2({
27043
+ apiKey,
27044
+ apiBaseUrl,
27045
+ fn: "joe_command_status",
27046
+ body: { command_id: commandId },
27047
+ operation: "Failed to fetch command status",
27048
+ debug
27049
+ });
27050
+ }
27051
+ async function getCommandResult2(params) {
27052
+ const { apiKey, apiBaseUrl, commandId, debug } = params;
27053
+ if (!commandId) {
27054
+ throw new Error("commandId is required");
27055
+ }
27056
+ return callRpc2({
27057
+ apiKey,
27058
+ apiBaseUrl,
27059
+ fn: "joe_command_result",
27060
+ body: { command_id: commandId },
27061
+ operation: "Failed to fetch command result",
27062
+ debug
27063
+ });
27064
+ }
27065
+ function normalizeProjectRow2(row) {
27066
+ const projectId = row.project_id ?? row.id ?? 0;
27067
+ return {
27068
+ project_id: Number(projectId),
27069
+ alias: row.alias ?? null,
27070
+ name: row.name ?? null,
27071
+ label: row.label ?? null,
27072
+ joe_ready: Boolean(row.joe_ready ?? row.joe_api_v2_enabled ?? false),
27073
+ tunnel: Boolean(row.tunnel ?? row.tunnel_ready ?? false)
27074
+ };
27075
+ }
27076
+ async function listProjects2(params) {
27077
+ const { apiKey, apiBaseUrl, orgId, debug } = params;
27078
+ const body = {};
27079
+ if (typeof orgId === "number") {
27080
+ body.org_id = orgId;
27081
+ }
27082
+ const rows = await callRpc2({
27083
+ apiKey,
27084
+ apiBaseUrl,
27085
+ fn: "projects_list",
27086
+ body,
27087
+ operation: "Failed to list projects",
27088
+ debug
27089
+ });
27090
+ if (!Array.isArray(rows)) {
27091
+ return [];
27092
+ }
27093
+ return rows.map(normalizeProjectRow2);
27094
+ }
27095
+ function isNumericProjectRef3(ref) {
27096
+ return /^[0-9]+$/.test(ref.trim());
27097
+ }
27098
+ async function resolveProjectId2(params) {
27099
+ const ref = String(params.project ?? "").trim();
27100
+ if (!ref) {
27101
+ throw new Error("project is required (--project <id|alias>)");
27102
+ }
27103
+ if (isNumericProjectRef3(ref)) {
27104
+ return Number(ref);
27105
+ }
27106
+ const projects = await listProjects2({
27107
+ apiKey: params.apiKey,
27108
+ apiBaseUrl: params.apiBaseUrl,
27109
+ orgId: params.orgId,
27110
+ debug: params.debug
27111
+ });
27112
+ const needle = ref.toLowerCase();
27113
+ const match = projects.find((p) => p.alias !== null && p.alias.toLowerCase() === needle || p.name !== null && p.name.toLowerCase() === needle);
27114
+ if (!match) {
27115
+ throw new Error(`Project not found for alias/name '${ref}'. Run 'pgai projects' to see available projects.`);
27116
+ }
27117
+ return match.project_id;
27118
+ }
27119
+ function sessionStorePath2(dir) {
27120
+ return path5.join(dir ?? getConfigDir2(), "joe-sessions.json");
27121
+ }
27122
+ function readSessionStore2(dir) {
27123
+ const file = sessionStorePath2(dir);
27124
+ if (!fs5.existsSync(file)) {
27125
+ return {};
27126
+ }
27127
+ try {
27128
+ const parsed = JSON.parse(fs5.readFileSync(file, "utf8"));
27129
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
27130
+ return parsed;
27131
+ }
27132
+ } catch {}
27133
+ return {};
27134
+ }
27135
+ function readStoredSessionId2(projectId, dir) {
27136
+ const store = readSessionStore2(dir);
27137
+ const value = store[String(projectId)];
27138
+ return typeof value === "string" && value.length > 0 ? value : null;
27139
+ }
27140
+ function writeStoredSessionId2(projectId, sessionId, dir) {
27141
+ const baseDir = dir ?? getConfigDir2();
27142
+ if (!fs5.existsSync(baseDir)) {
27143
+ fs5.mkdirSync(baseDir, { recursive: true, mode: 448 });
27144
+ }
27145
+ const store = readSessionStore2(dir);
27146
+ store[String(projectId)] = sessionId;
27147
+ fs5.writeFileSync(sessionStorePath2(dir), JSON.stringify(store, null, 2) + `
27148
+ `, { mode: 384 });
27149
+ }
27150
+ function clearStoredSessionId2(projectId, dir) {
27151
+ const file = sessionStorePath2(dir);
27152
+ if (!fs5.existsSync(file)) {
27153
+ return;
27154
+ }
27155
+ const store = readSessionStore2(dir);
27156
+ if (String(projectId) in store) {
27157
+ delete store[String(projectId)];
27158
+ fs5.writeFileSync(file, JSON.stringify(store, null, 2) + `
27159
+ `, { mode: 384 });
27160
+ }
27161
+ }
27162
+ function computeIdempotencyKey2(command, projectId, sql, args) {
27163
+ if (EXECUTING_COMMANDS2.has(command)) {
27164
+ const canonical = `${projectId}
27165
+ ${command}
27166
+ ${sql ?? ""}
27167
+ ${JSON.stringify(args ?? null)}`;
27168
+ return `joe-${crypto2.createHash("sha256").update(canonical).digest("hex")}`;
27169
+ }
27170
+ return `joe-${crypto2.randomUUID()}`;
27171
+ }
27172
+ var defaultSleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
27173
+ async function runCommand2(params) {
27174
+ const {
27175
+ apiKey,
27176
+ apiBaseUrl,
27177
+ command,
27178
+ projectId,
27179
+ sql,
27180
+ args,
27181
+ sessionId,
27182
+ debug
27183
+ } = params;
27184
+ const budgetMs = params.budgetMs ?? DEFAULT_BUDGET_MS2;
27185
+ const pollIntervalMs = params.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS2;
27186
+ const now = params.now ?? Date.now;
27187
+ const sleep = params.sleep ?? defaultSleep2;
27188
+ const idempotencyKey = params.idempotencyKey ?? computeIdempotencyKey2(command, projectId, sql, args);
27189
+ const submitted = await submitCommand2({
27190
+ apiKey,
27191
+ apiBaseUrl,
27192
+ command,
27193
+ projectId,
27194
+ sql,
27195
+ args,
27196
+ sessionId,
27197
+ idempotencyKey,
27198
+ debug
27199
+ });
27200
+ const commandId = submitted.command_id;
27201
+ const newSessionId = submitted.session_id ?? sessionId ?? null;
27202
+ const deadline = now() + budgetMs;
27203
+ let status = submitted.status;
27204
+ while (true) {
27205
+ const statusResp = await getCommandStatus2({ apiKey, apiBaseUrl, commandId, debug });
27206
+ status = statusResp.status;
27207
+ if (TERMINAL_STATES2.has(status)) {
27208
+ break;
27209
+ }
27210
+ if (now() >= deadline) {
27211
+ return { commandId, sessionId: newSessionId, status, result: null, budgetExpired: true };
27212
+ }
27213
+ await sleep(pollIntervalMs);
27214
+ }
27215
+ const result = await getCommandResult2({ apiKey, apiBaseUrl, commandId, debug });
27216
+ return { commandId, sessionId: newSessionId, status, result, budgetExpired: false };
27217
+ }
27218
+ async function executeJoeCommand2(params) {
27219
+ const projectId = await resolveProjectId2({
27220
+ apiKey: params.apiKey,
27221
+ apiBaseUrl: params.apiBaseUrl,
27222
+ project: params.project,
27223
+ orgId: params.orgId,
27224
+ debug: params.debug
27225
+ });
27226
+ let sessionId;
27227
+ if (params.newSession) {
27228
+ clearStoredSessionId2(projectId, params.sessionDir);
27229
+ sessionId = null;
27230
+ } else if (params.session) {
27231
+ sessionId = String(params.session);
27232
+ } else {
27233
+ sessionId = readStoredSessionId2(projectId, params.sessionDir);
27234
+ }
27235
+ const outcome = await runCommand2({
27236
+ apiKey: params.apiKey,
27237
+ apiBaseUrl: params.apiBaseUrl,
27238
+ command: params.command,
27239
+ projectId,
27240
+ sql: params.sql,
27241
+ args: params.args,
27242
+ sessionId,
27243
+ budgetMs: params.budgetMs,
27244
+ pollIntervalMs: params.pollIntervalMs,
27245
+ debug: params.debug,
27246
+ now: params.now,
27247
+ sleep: params.sleep
27248
+ });
27249
+ if (outcome.sessionId) {
27250
+ writeStoredSessionId2(projectId, outcome.sessionId, params.sessionDir);
27251
+ }
27252
+ return { ...outcome, command: params.command, projectId };
27253
+ }
27254
+ function clientSidePlanFlags(planJson) {
27255
+ const flags = [];
27256
+ const walk = (node) => {
27257
+ if (!node || typeof node !== "object") {
27258
+ return;
27259
+ }
27260
+ const nodeType = node["Node Type"];
27261
+ if (nodeType === "Seq Scan") {
27262
+ const rel = node["Relation Name"];
27263
+ flags.push(`client-side: Seq Scan${rel ? ` on ${rel}` : ""} — no index serves this predicate; consider adding one.`);
27264
+ }
27265
+ if (Array.isArray(node.Plans)) {
27266
+ for (const child of node.Plans) {
27267
+ walk(child);
27268
+ }
27269
+ }
27270
+ };
27271
+ if (planJson && typeof planJson === "object") {
27272
+ const root = planJson;
27273
+ walk(root.Plan ?? planJson);
27274
+ }
27275
+ return flags;
27276
+ }
27277
+ function formatJoeResult(command, result) {
27278
+ const lines = [];
27279
+ switch (command) {
27280
+ case "plan":
27281
+ case "explain": {
27282
+ if (result.plan_text) {
27283
+ lines.push(result.plan_text);
27284
+ }
27285
+ for (const flag of clientSidePlanFlags(result.plan_json)) {
27286
+ lines.push(`⚑ ${flag}`);
27287
+ }
27288
+ if (result.queryid || result.plan_fingerprint) {
27289
+ lines.push(`(queryid ${result.queryid ?? "?"} · plan_fp ${result.plan_fingerprint ?? "?"})`);
27290
+ }
27291
+ break;
27292
+ }
27293
+ case "exec": {
27294
+ const notices = result.notices ?? [];
27295
+ const rowCount = result.row_count ?? 0;
27296
+ lines.push(`${notices.join(" ") || "OK"} · ${rowCount} row${rowCount === 1 ? "" : "s"}`);
27297
+ if (result.result_rows && result.result_rows.length > 0) {
27298
+ lines.push(JSON.stringify(result.result_rows, null, 2));
27299
+ }
27300
+ break;
27301
+ }
27302
+ case "hypo": {
27303
+ lines.push(result.hypo_used ? "hypothetical index would be used" : "hypothetical index would NOT be used");
27304
+ if (result.hypo_plan !== undefined) {
27305
+ lines.push(JSON.stringify(result.hypo_plan, null, 2));
27306
+ }
27307
+ break;
27308
+ }
27309
+ case "activity":
27310
+ case "describe": {
27311
+ lines.push(JSON.stringify(result.snapshot ?? null, null, 2));
27312
+ break;
27313
+ }
27314
+ case "terminate": {
27315
+ lines.push(`terminated ${result.terminated ? "yes" : "no"} · pid ${result.pid ?? "?"}`);
27316
+ break;
27317
+ }
27318
+ case "reset": {
27319
+ lines.push(`reset ${result.reset ? "ok" : "no"}`);
27320
+ break;
27321
+ }
27322
+ }
27323
+ return lines.join(`
27324
+ `);
27325
+ }
27326
+ function formatProjectsTable(projects) {
27327
+ const header = ["PROJECT_ID", "ALIAS", "PROJECT", "JOE", "TUNNEL"];
27328
+ const rows = projects.map((p) => [
27329
+ String(p.project_id),
27330
+ p.alias ?? "-",
27331
+ p.name ?? "-",
27332
+ p.joe_ready ? "ready" : "no",
27333
+ p.tunnel ? "yes" : "no"
27334
+ ]);
27335
+ const widths = header.map((h, i2) => Math.max(h.length, ...rows.map((r) => r[i2].length), 0));
27336
+ const pad = (cells) => cells.map((c, i2) => c.padEnd(i2 === cells.length - 1 ? 0 : widths[i2])).join(" ").trimEnd();
27337
+ return [pad(header), ...rows.map(pad)].join(`
27338
+ `);
27339
+ }
27340
+
27341
+ // lib/dblab.ts
27342
+ function isNumericProjectRef4(ref) {
27343
+ return /^[0-9]+$/.test(ref.trim());
27344
+ }
27345
+ async function resolveDblabInstanceId2(params) {
27346
+ const { apiKey, apiBaseUrl, orgId, debug } = params;
27347
+ if (!apiKey) {
27348
+ throw new Error("API key is required");
27349
+ }
27350
+ const ref = String(params.project ?? "").trim();
27351
+ if (!ref) {
27352
+ throw new Error("project is required (--project <id|alias>)");
27353
+ }
27354
+ const base = normalizeBaseUrl(apiBaseUrl);
27355
+ const url = new URL(`${base}/dblab_instances`);
27356
+ url.searchParams.set("select", "id,project_id,project_name,project_alias,project_label_or_name,org_id");
27357
+ if (typeof orgId === "number") {
27358
+ url.searchParams.set("org_id", `eq.${orgId}`);
27359
+ }
27360
+ const headers = {
27361
+ "access-token": apiKey,
27362
+ "Content-Type": "application/json",
27363
+ Connection: "close"
27364
+ };
27365
+ if (debug) {
27366
+ const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
27367
+ console.error(`Debug: GET URL: ${url.toString()}`);
27368
+ console.error(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
27369
+ }
27370
+ const response = await fetch(url.toString(), { method: "GET", headers });
27371
+ const text = await response.text();
27372
+ if (debug) {
27373
+ console.error(`Debug: Response status: ${response.status}`);
27374
+ console.error(`Debug: Response body: ${text}`);
27375
+ }
27376
+ if (!response.ok) {
27377
+ throw new Error(formatHttpError("Failed to resolve project's DBLab instance", response.status, text));
27378
+ }
27379
+ let rows;
27380
+ try {
27381
+ const parsed = JSON.parse(text);
27382
+ rows = Array.isArray(parsed) ? parsed : [];
27383
+ } catch {
27384
+ throw new Error(`Failed to parse DBLab instances response: ${text}`);
27385
+ }
27386
+ const numeric = isNumericProjectRef4(ref);
27387
+ const needle = ref.toLowerCase();
27388
+ const match = rows.find((r) => {
27389
+ if (numeric) {
27390
+ return String(r.project_id ?? "") === ref;
27391
+ }
27392
+ return r.project_alias != null && String(r.project_alias).toLowerCase() === needle || r.project_name != null && String(r.project_name).toLowerCase() === needle || r.project_label_or_name != null && String(r.project_label_or_name).toLowerCase() === needle;
27393
+ });
27394
+ if (!match) {
27395
+ throw new Error(`No DBLab instance found for project '${ref}'. Run 'pgai projects' to see available projects.`);
27396
+ }
27397
+ return String(match.id);
27398
+ }
27399
+ async function callDblabApi2(params) {
27400
+ const { apiKey, apiBaseUrl, instanceId, action, method, data, operation, debug } = params;
27401
+ if (!apiKey) {
27402
+ throw new Error("API key is required");
27403
+ }
27404
+ if (!instanceId) {
27405
+ throw new Error("instanceId is required");
27406
+ }
27407
+ const base = normalizeBaseUrl(apiBaseUrl);
27408
+ const url = new URL(`${base}/rpc/dblab_api_call`);
27409
+ const bodyObj = {
27410
+ instance_id: instanceId,
27411
+ action,
27412
+ method
27413
+ };
27414
+ if (data !== undefined) {
27415
+ bodyObj.data = data;
27416
+ }
27417
+ const body = JSON.stringify(bodyObj);
27418
+ const headers = {
27419
+ "access-token": apiKey,
27420
+ "Content-Type": "application/json",
27421
+ Connection: "close"
27422
+ };
27423
+ if (debug) {
27424
+ const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
27425
+ console.error(`Debug: POST URL: ${url.toString()}`);
27426
+ console.error(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
27427
+ console.error(`Debug: Request body: ${body}`);
27428
+ }
27429
+ const response = await fetch(url.toString(), { method: "POST", headers, body });
27430
+ const text = await response.text();
27431
+ if (debug) {
27432
+ console.error(`Debug: Response status: ${response.status}`);
27433
+ console.error(`Debug: Response body: ${text}`);
27434
+ }
27435
+ if (!response.ok) {
27436
+ throw new Error(formatHttpError(operation, response.status, text));
27437
+ }
27438
+ if (text.trim() === "") {
27439
+ return null;
27440
+ }
27441
+ try {
27442
+ return JSON.parse(text);
27443
+ } catch {
27444
+ throw new Error(`${operation}: failed to parse response: ${text}`);
27445
+ }
27446
+ }
27447
+ async function createClone2(params) {
27448
+ const { apiKey, apiBaseUrl, instanceId, cloneId, branch, snapshotId, dbUser, dbPassword, isProtected, debug } = params;
27449
+ const data = { protected: Boolean(isProtected) };
27450
+ if (cloneId)
27451
+ data.id = cloneId;
27452
+ if (branch)
27453
+ data.branch = branch;
27454
+ if (snapshotId)
27455
+ data.snapshot = { id: snapshotId };
27456
+ if (dbUser && dbPassword)
27457
+ data.db = { username: dbUser, password: dbPassword };
27458
+ return callDblabApi2({
27459
+ apiKey,
27460
+ apiBaseUrl,
27461
+ instanceId,
27462
+ action: "/clone",
27463
+ method: "post",
27464
+ data,
27465
+ operation: "Failed to create clone",
27466
+ debug
27467
+ });
27468
+ }
27469
+ async function listClones2(params) {
27470
+ const { apiKey, apiBaseUrl, instanceId, debug } = params;
27471
+ return callDblabApi2({
27472
+ apiKey,
27473
+ apiBaseUrl,
27474
+ instanceId,
27475
+ action: "/clones",
27476
+ method: "get",
27477
+ operation: "Failed to list clones",
27478
+ debug
27479
+ });
27480
+ }
27481
+ async function getClone2(params) {
27482
+ const { apiKey, apiBaseUrl, instanceId, cloneId, debug } = params;
27483
+ if (!cloneId)
27484
+ throw new Error("cloneId is required");
27485
+ return callDblabApi2({
27486
+ apiKey,
27487
+ apiBaseUrl,
27488
+ instanceId,
27489
+ action: `/clone/${encodeURIComponent(cloneId)}`,
27490
+ method: "get",
27491
+ operation: "Failed to get clone",
27492
+ debug
27493
+ });
27494
+ }
27495
+ async function resetClone2(params) {
27496
+ const { apiKey, apiBaseUrl, instanceId, cloneId, snapshotId, latest, debug } = params;
27497
+ if (!cloneId)
27498
+ throw new Error("cloneId is required");
27499
+ const data = { latest: latest ?? !snapshotId };
27500
+ if (snapshotId)
27501
+ data.snapshotID = snapshotId;
27502
+ return callDblabApi2({
27503
+ apiKey,
27504
+ apiBaseUrl,
27505
+ instanceId,
27506
+ action: `/clone/${encodeURIComponent(cloneId)}/reset`,
27507
+ method: "post",
27508
+ data,
27509
+ operation: "Failed to reset clone",
27510
+ debug
27511
+ });
27512
+ }
27513
+ async function destroyClone2(params) {
27514
+ const { apiKey, apiBaseUrl, instanceId, cloneId, debug } = params;
27515
+ if (!cloneId)
27516
+ throw new Error("cloneId is required");
27517
+ return callDblabApi2({
27518
+ apiKey,
27519
+ apiBaseUrl,
27520
+ instanceId,
27521
+ action: `/clone/${encodeURIComponent(cloneId)}`,
27522
+ method: "delete",
27523
+ operation: "Failed to destroy clone",
27524
+ debug
27525
+ });
27526
+ }
27527
+ async function listBranches2(params) {
27528
+ const { apiKey, apiBaseUrl, instanceId, debug } = params;
27529
+ return callDblabApi2({
27530
+ apiKey,
27531
+ apiBaseUrl,
27532
+ instanceId,
27533
+ action: "/branches",
27534
+ method: "get",
27535
+ operation: "Failed to list branches",
27536
+ debug
27537
+ });
27538
+ }
27539
+ async function createBranch2(params) {
27540
+ const { apiKey, apiBaseUrl, instanceId, branchName, baseBranch, snapshotId, debug } = params;
27541
+ if (!branchName)
27542
+ throw new Error("branchName is required");
27543
+ const data = { branchName };
27544
+ if (baseBranch)
27545
+ data.baseBranch = baseBranch;
27546
+ if (snapshotId)
27547
+ data.snapshotID = snapshotId;
27548
+ return callDblabApi2({
27549
+ apiKey,
27550
+ apiBaseUrl,
27551
+ instanceId,
27552
+ action: "/branch",
27553
+ method: "post",
27554
+ data,
27555
+ operation: "Failed to create branch",
27556
+ debug
27557
+ });
27558
+ }
27559
+ async function deleteBranch2(params) {
27560
+ const { apiKey, apiBaseUrl, instanceId, branchName, debug } = params;
27561
+ if (!branchName)
27562
+ throw new Error("branchName is required");
27563
+ return callDblabApi2({
27564
+ apiKey,
27565
+ apiBaseUrl,
27566
+ instanceId,
27567
+ action: `/branch/${encodeURIComponent(branchName)}`,
27568
+ method: "delete",
27569
+ operation: "Failed to delete branch",
27570
+ debug
27571
+ });
27572
+ }
27573
+ async function branchLog2(params) {
27574
+ const { apiKey, apiBaseUrl, instanceId, branchName, debug } = params;
27575
+ if (!branchName)
27576
+ throw new Error("branchName is required");
27577
+ return callDblabApi2({
27578
+ apiKey,
27579
+ apiBaseUrl,
27580
+ instanceId,
27581
+ action: `/branch/${encodeURIComponent(branchName)}/log`,
27582
+ method: "get",
27583
+ operation: "Failed to fetch branch log",
27584
+ debug
27585
+ });
27586
+ }
27587
+ async function listSnapshots2(params) {
27588
+ const { apiKey, apiBaseUrl, instanceId, branchName, dataset, debug } = params;
27589
+ const qs = new URLSearchParams;
27590
+ const branch = branchName?.trim();
27591
+ if (branch)
27592
+ qs.append("branch", branch);
27593
+ if (dataset)
27594
+ qs.append("dataset", dataset);
27595
+ const action = `/snapshots${qs.toString() ? `?${qs.toString()}` : ""}`;
27596
+ return callDblabApi2({
27597
+ apiKey,
27598
+ apiBaseUrl,
27599
+ instanceId,
27600
+ action,
27601
+ method: "get",
27602
+ operation: "Failed to list snapshots",
27603
+ debug
27604
+ });
27605
+ }
27606
+ async function createSnapshot2(params) {
27607
+ const { apiKey, apiBaseUrl, instanceId, cloneId, message, debug } = params;
27608
+ if (!cloneId)
27609
+ throw new Error("cloneId is required");
27610
+ const data = { cloneID: cloneId };
27611
+ if (message)
27612
+ data.message = message;
27613
+ return callDblabApi2({
27614
+ apiKey,
27615
+ apiBaseUrl,
27616
+ instanceId,
27617
+ action: "/branch/snapshot",
27618
+ method: "post",
27619
+ data,
27620
+ operation: "Failed to create snapshot",
27621
+ debug
27622
+ });
27623
+ }
27624
+ async function destroySnapshot2(params) {
27625
+ const { apiKey, apiBaseUrl, instanceId, snapshotId, force, debug } = params;
27626
+ if (!snapshotId)
27627
+ throw new Error("snapshotId is required");
27628
+ const action = `/snapshot/${encodeURIComponent(snapshotId)}?force=${Boolean(force)}`;
27629
+ return callDblabApi2({
27630
+ apiKey,
27631
+ apiBaseUrl,
27632
+ instanceId,
27633
+ action,
27634
+ method: "delete",
27635
+ operation: "Failed to destroy snapshot",
27636
+ debug
27637
+ });
27638
+ }
27639
+
25804
27640
  // bin/postgres-ai.ts
25805
27641
  init_util();
25806
27642
 
25807
27643
  // lib/instances.ts
25808
- import * as fs4 from "fs";
27644
+ import * as fs6 from "fs";
25809
27645
 
25810
27646
  // node_modules/js-yaml/dist/js-yaml.mjs
25811
27647
  /*! js-yaml 4.1.1 https://github.com/nodeca/js-yaml @license MIT */
@@ -28470,11 +30306,11 @@ class InstancesParseError extends Error {
28470
30306
  }
28471
30307
  }
28472
30308
  function loadInstances(file) {
28473
- if (!fs4.existsSync(file))
30309
+ if (!fs6.existsSync(file))
28474
30310
  return [];
28475
- if (fs4.lstatSync(file).isDirectory())
30311
+ if (fs6.lstatSync(file).isDirectory())
28476
30312
  return [];
28477
- const text = fs4.readFileSync(file, "utf8");
30313
+ const text = fs6.readFileSync(file, "utf8");
28478
30314
  if (text.trim() === "")
28479
30315
  return [];
28480
30316
  let parsed;
@@ -28652,8 +30488,8 @@ async function registerAasCollection(apiKey, instanceId, opts) {
28652
30488
  }
28653
30489
 
28654
30490
  // lib/storage.ts
28655
- import * as fs5 from "fs";
28656
- import * as path4 from "path";
30491
+ import * as fs7 from "fs";
30492
+ import * as path6 from "path";
28657
30493
  var MAX_UPLOAD_SIZE2 = 500 * 1024 * 1024;
28658
30494
  var MAX_DOWNLOAD_SIZE2 = 500 * 1024 * 1024;
28659
30495
  var MIME_TYPES2 = {
@@ -28694,11 +30530,11 @@ async function uploadFile2(params) {
28694
30530
  if (!filePath) {
28695
30531
  throw new Error("filePath is required");
28696
30532
  }
28697
- const resolvedPath = path4.resolve(filePath);
28698
- if (!fs5.existsSync(resolvedPath)) {
30533
+ const resolvedPath = path6.resolve(filePath);
30534
+ if (!fs7.existsSync(resolvedPath)) {
28699
30535
  throw new Error(`File not found: ${resolvedPath}`);
28700
30536
  }
28701
- const stat = fs5.statSync(resolvedPath);
30537
+ const stat = fs7.statSync(resolvedPath);
28702
30538
  if (!stat.isFile()) {
28703
30539
  throw new Error(`Not a file: ${resolvedPath}`);
28704
30540
  }
@@ -28710,9 +30546,9 @@ async function uploadFile2(params) {
28710
30546
  console.error("Warning: storage URL uses HTTP — API key will be sent unencrypted");
28711
30547
  }
28712
30548
  const url = `${base}/upload`;
28713
- const fileBuffer = fs5.readFileSync(resolvedPath);
28714
- const fileName = path4.basename(resolvedPath);
28715
- const ext = path4.extname(fileName).toLowerCase();
30549
+ const fileBuffer = fs7.readFileSync(resolvedPath);
30550
+ const fileName = path6.basename(resolvedPath);
30551
+ const ext = path6.extname(fileName).toLowerCase();
28716
30552
  const mimeType = MIME_TYPES2[ext] || "application/octet-stream";
28717
30553
  const formData = new FormData;
28718
30554
  formData.append("file", new Blob([fileBuffer], { type: mimeType }), fileName);
@@ -28771,15 +30607,15 @@ async function downloadFile2(params) {
28771
30607
  const relativePath = fileUrl.startsWith("/") ? fileUrl : `/${fileUrl}`;
28772
30608
  fullUrl = `${base}${relativePath}`;
28773
30609
  }
28774
- const urlFilename = path4.basename(new URL(fullUrl).pathname);
30610
+ const urlFilename = path6.basename(new URL(fullUrl).pathname);
28775
30611
  if (!urlFilename) {
28776
30612
  throw new Error("Cannot derive filename from URL; please specify --output");
28777
30613
  }
28778
- const saveTo = outputPath ? path4.resolve(outputPath) : path4.resolve(urlFilename);
30614
+ const saveTo = outputPath ? path6.resolve(outputPath) : path6.resolve(urlFilename);
28779
30615
  if (!outputPath) {
28780
- const normalizedSave = path4.normalize(saveTo);
28781
- const cwd = path4.normalize(process.cwd());
28782
- if (normalizedSave !== cwd && !normalizedSave.startsWith(cwd + path4.sep)) {
30616
+ const normalizedSave = path6.normalize(saveTo);
30617
+ const cwd = path6.normalize(process.cwd());
30618
+ if (normalizedSave !== cwd && !normalizedSave.startsWith(cwd + path6.sep)) {
28783
30619
  throw new Error("Derived output path escapes current directory; please specify --output");
28784
30620
  }
28785
30621
  }
@@ -28811,11 +30647,11 @@ async function downloadFile2(params) {
28811
30647
  }
28812
30648
  const arrayBuffer = await response.arrayBuffer();
28813
30649
  const buffer = Buffer.from(arrayBuffer);
28814
- const parentDir = path4.dirname(saveTo);
28815
- if (!fs5.existsSync(parentDir)) {
28816
- fs5.mkdirSync(parentDir, { recursive: true });
30650
+ const parentDir = path6.dirname(saveTo);
30651
+ if (!fs7.existsSync(parentDir)) {
30652
+ fs7.mkdirSync(parentDir, { recursive: true });
28817
30653
  }
28818
- fs5.writeFileSync(saveTo, buffer);
30654
+ fs7.writeFileSync(saveTo, buffer);
28819
30655
  return {
28820
30656
  savedTo: saveTo,
28821
30657
  size: buffer.length,
@@ -28827,9 +30663,9 @@ function buildMarkdownLink2(fileUrl, storageBaseUrl, filename) {
28827
30663
  const base = normalizeBaseUrl(storageBaseUrl);
28828
30664
  const normalizedFileUrl = fileUrl.startsWith("/") ? fileUrl : `/${fileUrl}`;
28829
30665
  const fullUrl = fileUrl.startsWith("http://") || fileUrl.startsWith("https://") ? fileUrl : `${base}${normalizedFileUrl}`;
28830
- const name = filename || path4.basename(new URL(fullUrl).pathname);
30666
+ const name = filename || path6.basename(new URL(fullUrl).pathname);
28831
30667
  const safeName = name.replace(/[\[\]()]/g, "\\$&");
28832
- const ext = path4.extname(name).toLowerCase();
30668
+ const ext = path6.extname(name).toLowerCase();
28833
30669
  if (IMAGE_EXTENSIONS2.has(ext)) {
28834
30670
  return `![${safeName}](${fullUrl})`;
28835
30671
  }
@@ -28875,8 +30711,8 @@ ${links}`;
28875
30711
  // lib/init.ts
28876
30712
  import { randomBytes } from "crypto";
28877
30713
  import { URL as URL2, fileURLToPath } from "url";
28878
- import * as fs6 from "fs";
28879
- import * as path5 from "path";
30714
+ import * as fs8 from "fs";
30715
+ import * as path7 from "path";
28880
30716
  var DEFAULT_MONITORING_USER = "postgres_ai_mon";
28881
30717
  var KNOWN_PROVIDERS = ["self-managed", "supabase"];
28882
30718
  var SKIP_ROLE_CREATION_PROVIDERS = ["supabase"];
@@ -28968,21 +30804,21 @@ async function connectWithSslFallback(ClientClass, adminConn, verbose) {
28968
30804
  }
28969
30805
  function sqlDir() {
28970
30806
  const currentFile = fileURLToPath(import.meta.url);
28971
- const currentDir = path5.dirname(currentFile);
30807
+ const currentDir = path7.dirname(currentFile);
28972
30808
  const candidates = [
28973
- path5.resolve(currentDir, "..", "sql"),
28974
- path5.resolve(currentDir, "..", "..", "sql")
30809
+ path7.resolve(currentDir, "..", "sql"),
30810
+ path7.resolve(currentDir, "..", "..", "sql")
28975
30811
  ];
28976
30812
  for (const candidate of candidates) {
28977
- if (fs6.existsSync(candidate)) {
30813
+ if (fs8.existsSync(candidate)) {
28978
30814
  return candidate;
28979
30815
  }
28980
30816
  }
28981
30817
  throw new Error(`SQL directory not found. Searched: ${candidates.join(", ")}`);
28982
30818
  }
28983
30819
  function loadSqlTemplate(filename) {
28984
- const p = path5.join(sqlDir(), filename);
28985
- return fs6.readFileSync(p, "utf8");
30820
+ const p = path7.join(sqlDir(), filename);
30821
+ return fs8.readFileSync(p, "utf8");
28986
30822
  }
28987
30823
  function applyTemplate(sql, vars) {
28988
30824
  return sql.replace(/\{\{([A-Z0-9_]+)\}\}/g, (_, key) => {
@@ -29452,10 +31288,6 @@ async function verifyInitSetup(params) {
29452
31288
  }
29453
31289
  }
29454
31290
  }
29455
- const explainFnRes = await params.client.query("select has_function_privilege($1, 'postgres_ai.explain_generic(text, text, text)', 'EXECUTE') as ok", [role]);
29456
- if (!explainFnRes.rows?.[0]?.ok) {
29457
- missingRequired.push("EXECUTE on postgres_ai.explain_generic(text, text, text)");
29458
- }
29459
31291
  const tableDescribeFnRes = await params.client.query("select has_function_privilege($1, 'postgres_ai.table_describe(text)', 'EXECUTE') as ok", [role]);
29460
31292
  if (!tableDescribeFnRes.rows?.[0]?.ok) {
29461
31293
  missingRequired.push("EXECUTE on postgres_ai.table_describe(text)");
@@ -29974,15 +31806,6 @@ async function verifyInitSetupViaSupabase(params) {
29974
31806
  missingRequired.push("role search_path includes postgres_ai, public and pg_catalog");
29975
31807
  }
29976
31808
  }
29977
- const explainFnExistsRes = await params.client.query("SELECT oid FROM pg_proc WHERE proname = 'explain_generic' AND pronamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'postgres_ai')", true);
29978
- if (explainFnExistsRes.rowCount === 0) {
29979
- missingRequired.push("function postgres_ai.explain_generic exists");
29980
- } else {
29981
- const explainFnRes = await params.client.query(`SELECT has_function_privilege('${escapeLiteral2(role)}', 'postgres_ai.explain_generic(text, text, text)', 'EXECUTE') as ok`, true);
29982
- if (!explainFnRes.rows?.[0]?.ok) {
29983
- missingRequired.push("EXECUTE on postgres_ai.explain_generic(text, text, text)");
29984
- }
29985
- }
29986
31809
  const tableDescribeFnExistsRes = await params.client.query("SELECT oid FROM pg_proc WHERE proname = 'table_describe' AND pronamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'postgres_ai')", true);
29987
31810
  if (tableDescribeFnExistsRes.rowCount === 0) {
29988
31811
  missingRequired.push("function postgres_ai.table_describe exists");
@@ -30040,9 +31863,9 @@ function escapeLiteral2(value) {
30040
31863
  }
30041
31864
 
30042
31865
  // lib/pkce.ts
30043
- import * as crypto from "crypto";
31866
+ import * as crypto3 from "crypto";
30044
31867
  function generateRandomString(length = 64) {
30045
- const bytes = crypto.randomBytes(length);
31868
+ const bytes = crypto3.randomBytes(length);
30046
31869
  return base64URLEncode(bytes);
30047
31870
  }
30048
31871
  function base64URLEncode(buffer) {
@@ -30052,7 +31875,7 @@ function generateCodeVerifier() {
30052
31875
  return generateRandomString(32);
30053
31876
  }
30054
31877
  function generateCodeChallenge(verifier) {
30055
- const hash = crypto.createHash("sha256").update(verifier).digest();
31878
+ const hash = crypto3.createHash("sha256").update(verifier).digest();
30056
31879
  return base64URLEncode(hash);
30057
31880
  }
30058
31881
  function generateState() {
@@ -30281,8 +32104,8 @@ import { createInterface } from "readline";
30281
32104
  import * as childProcess from "child_process";
30282
32105
 
30283
32106
  // lib/checkup.ts
30284
- import * as fs7 from "fs";
30285
- import * as path6 from "path";
32107
+ import * as fs9 from "fs";
32108
+ import * as path8 from "path";
30286
32109
 
30287
32110
  // lib/metrics-embedded.ts
30288
32111
  var METRICS = {
@@ -32013,7 +33836,7 @@ function buildCheckInfoMap() {
32013
33836
  }
32014
33837
 
32015
33838
  // lib/checkup.ts
32016
- var __dirname = "/builds/postgres-ai/postgresai/cli/lib";
33839
+ var __dirname = "/home/agent/workspace/postgresai-joe-v2/cli/lib";
32017
33840
  var SECONDS_PER_DAY = 86400;
32018
33841
  var SECONDS_PER_HOUR = 3600;
32019
33842
  var SECONDS_PER_MINUTE = 60;
@@ -32499,7 +34322,7 @@ function createBaseReport(checkId, checkTitle, nodeName) {
32499
34322
  }
32500
34323
  function readTextFileSafe(p) {
32501
34324
  try {
32502
- const value = fs7.readFileSync(p, "utf8").trim();
34325
+ const value = fs9.readFileSync(p, "utf8").trim();
32503
34326
  return value || null;
32504
34327
  } catch {
32505
34328
  return null;
@@ -32512,8 +34335,8 @@ function resolveBuildTs() {
32512
34335
  if (fromFile)
32513
34336
  return fromFile;
32514
34337
  try {
32515
- const pkgRoot = path6.resolve(__dirname, "..");
32516
- const fromPkgFile = readTextFileSafe(path6.join(pkgRoot, "BUILD_TS"));
34338
+ const pkgRoot = path8.resolve(__dirname, "..");
34339
+ const fromPkgFile = readTextFileSafe(path8.join(pkgRoot, "BUILD_TS"));
32517
34340
  if (fromPkgFile)
32518
34341
  return fromPkgFile;
32519
34342
  } catch (err) {
@@ -32521,8 +34344,8 @@ function resolveBuildTs() {
32521
34344
  console.warn(`[resolveBuildTs] Warning: path resolution failed: ${errorMsg}`);
32522
34345
  }
32523
34346
  try {
32524
- const pkgJsonPath = path6.resolve(__dirname, "..", "package.json");
32525
- const st = fs7.statSync(pkgJsonPath);
34347
+ const pkgJsonPath = path8.resolve(__dirname, "..", "package.json");
34348
+ const st = fs9.statSync(pkgJsonPath);
32526
34349
  return st.mtime.toISOString();
32527
34350
  } catch (err) {
32528
34351
  if (process.env.DEBUG) {
@@ -33803,7 +35626,7 @@ function summarizeG003(nodeData) {
33803
35626
  }
33804
35627
 
33805
35628
  // lib/instances.ts
33806
- import * as fs8 from "fs";
35629
+ import * as fs10 from "fs";
33807
35630
  class InstancesParseError2 extends Error {
33808
35631
  constructor(file, cause) {
33809
35632
  const causeMsg = cause instanceof Error ? cause.message : String(cause);
@@ -33812,11 +35635,11 @@ class InstancesParseError2 extends Error {
33812
35635
  }
33813
35636
  }
33814
35637
  function loadInstances2(file) {
33815
- if (!fs8.existsSync(file))
35638
+ if (!fs10.existsSync(file))
33816
35639
  return [];
33817
- if (fs8.lstatSync(file).isDirectory())
35640
+ if (fs10.lstatSync(file).isDirectory())
33818
35641
  return [];
33819
- const text = fs8.readFileSync(file, "utf8");
35642
+ const text = fs10.readFileSync(file, "utf8");
33820
35643
  if (text.trim() === "")
33821
35644
  return [];
33822
35645
  let parsed;
@@ -33849,22 +35672,22 @@ function buildInstance(name, connStr) {
33849
35672
  };
33850
35673
  }
33851
35674
  function addInstanceToFile(file, instance) {
33852
- if (fs8.existsSync(file) && fs8.lstatSync(file).isDirectory()) {
33853
- fs8.rmSync(file, { recursive: true, force: true });
35675
+ if (fs10.existsSync(file) && fs10.lstatSync(file).isDirectory()) {
35676
+ fs10.rmSync(file, { recursive: true, force: true });
33854
35677
  }
33855
35678
  const existing = loadInstances2(file);
33856
35679
  if (existing.some((i3) => i3.name === instance.name)) {
33857
35680
  throw new Error(`Monitoring target '${instance.name}' already exists`);
33858
35681
  }
33859
35682
  existing.push(instance);
33860
- fs8.writeFileSync(file, dump2(existing), "utf8");
35683
+ fs10.writeFileSync(file, dump2(existing), "utf8");
33861
35684
  }
33862
35685
  function removeInstanceFromFile(file, name) {
33863
35686
  const instances = loadInstances2(file);
33864
35687
  const filtered = instances.filter((i3) => i3.name !== name);
33865
35688
  if (filtered.length === instances.length)
33866
35689
  return false;
33867
- fs8.writeFileSync(file, dump2(filtered), "utf8");
35690
+ fs10.writeFileSync(file, dump2(filtered), "utf8");
33868
35691
  return true;
33869
35692
  }
33870
35693
  function extractSslmode(connStr) {
@@ -33959,13 +35782,13 @@ function stripMatchingQuotes(value) {
33959
35782
  return trimmed;
33960
35783
  }
33961
35784
  var REQUIRED_ENV_KEYS = [
33962
- { key: "REPLICATOR_PASSWORD", defaultValue: () => crypto2.randomBytes(32).toString("hex"), introducedIn: "0.13" },
35785
+ { key: "REPLICATOR_PASSWORD", defaultValue: () => crypto4.randomBytes(32).toString("hex"), introducedIn: "0.13" },
33963
35786
  { key: "VM_AUTH_USERNAME", defaultValue: () => "vmauth", introducedIn: "0.15" },
33964
- { key: "VM_AUTH_PASSWORD", defaultValue: () => crypto2.randomBytes(18).toString("base64"), introducedIn: "0.15" }
35787
+ { key: "VM_AUTH_PASSWORD", defaultValue: () => crypto4.randomBytes(18).toString("base64"), introducedIn: "0.15" }
33965
35788
  ];
33966
35789
  function ensureRequiredEnvVars(projectDir) {
33967
- const envFile = path7.resolve(projectDir, ".env");
33968
- const existing = fs9.existsSync(envFile) ? fs9.readFileSync(envFile, "utf8") : "";
35790
+ const envFile = path9.resolve(projectDir, ".env");
35791
+ const existing = fs11.existsSync(envFile) ? fs11.readFileSync(envFile, "utf8") : "";
33969
35792
  const added = [];
33970
35793
  const appendLines = [];
33971
35794
  for (const spec of REQUIRED_ENV_KEYS) {
@@ -33984,7 +35807,7 @@ function ensureRequiredEnvVars(projectDir) {
33984
35807
  ` : "") + appendLines.join(`
33985
35808
  `) + `
33986
35809
  `;
33987
- fs9.writeFileSync(envFile, newContent, { encoding: "utf8", mode: 384 });
35810
+ fs11.writeFileSync(envFile, newContent, { encoding: "utf8", mode: 384 });
33988
35811
  return added;
33989
35812
  }
33990
35813
  async function execFilePromise(file, args) {
@@ -34049,7 +35872,7 @@ function expandHomePath(p) {
34049
35872
  if (s === "~")
34050
35873
  return os3.homedir();
34051
35874
  if (s.startsWith("~/") || s.startsWith("~\\")) {
34052
- return path7.join(os3.homedir(), s.slice(2));
35875
+ return path9.join(os3.homedir(), s.slice(2));
34053
35876
  }
34054
35877
  return s;
34055
35878
  }
@@ -34098,10 +35921,10 @@ function prepareOutputDirectory(outputOpt) {
34098
35921
  if (!outputOpt)
34099
35922
  return;
34100
35923
  const outputDir = expandHomePath(outputOpt);
34101
- const outputPath = path7.isAbsolute(outputDir) ? outputDir : path7.resolve(process.cwd(), outputDir);
34102
- if (!fs9.existsSync(outputPath)) {
35924
+ const outputPath = path9.isAbsolute(outputDir) ? outputDir : path9.resolve(process.cwd(), outputDir);
35925
+ if (!fs11.existsSync(outputPath)) {
34103
35926
  try {
34104
- fs9.mkdirSync(outputPath, { recursive: true });
35927
+ fs11.mkdirSync(outputPath, { recursive: true });
34105
35928
  } catch (e) {
34106
35929
  const errAny = e;
34107
35930
  const code = typeof errAny?.code === "string" ? errAny.code : "";
@@ -34137,7 +35960,7 @@ function prepareUploadConfig(opts, rootOpts, shouldUpload, uploadExplicitlyReque
34137
35960
  let project = (opts.project || cfg.defaultProject || "").trim();
34138
35961
  let projectWasGenerated = false;
34139
35962
  if (!project) {
34140
- project = `project_${crypto2.randomBytes(6).toString("hex")}`;
35963
+ project = `project_${crypto4.randomBytes(6).toString("hex")}`;
34141
35964
  projectWasGenerated = true;
34142
35965
  try {
34143
35966
  writeConfig({ defaultProject: project });
@@ -34183,8 +36006,8 @@ async function uploadCheckupReports(uploadCfg, reports, spinner, logUpload) {
34183
36006
  }
34184
36007
  function writeReportFiles(reports, outputPath) {
34185
36008
  for (const [checkId, report] of Object.entries(reports)) {
34186
- const filePath = path7.join(outputPath, `${checkId}.json`);
34187
- fs9.writeFileSync(filePath, JSON.stringify(report, null, 2), "utf8");
36009
+ const filePath = path9.join(outputPath, `${checkId}.json`);
36010
+ fs11.writeFileSync(filePath, JSON.stringify(report, null, 2), "utf8");
34188
36011
  const title = report.checkTitle || checkId;
34189
36012
  console.log(`\u2713 ${checkId} ${title}: ${filePath}`);
34190
36013
  }
@@ -34229,7 +36052,7 @@ function getDefaultMonitoringProjectDir() {
34229
36052
  const override = process.env.PGAI_PROJECT_DIR;
34230
36053
  if (override && override.trim())
34231
36054
  return override.trim();
34232
- return path7.join(getConfigDir(), "monitoring");
36055
+ return path9.join(getConfigDir(), "monitoring");
34233
36056
  }
34234
36057
  async function downloadText(url) {
34235
36058
  const controller = new AbortController;
@@ -34246,12 +36069,12 @@ async function downloadText(url) {
34246
36069
  }
34247
36070
  async function ensureDefaultMonitoringProject() {
34248
36071
  const projectDir = getDefaultMonitoringProjectDir();
34249
- const composeFile = path7.resolve(projectDir, "docker-compose.yml");
34250
- const instancesFile = path7.resolve(projectDir, "instances.yml");
34251
- if (!fs9.existsSync(projectDir)) {
34252
- fs9.mkdirSync(projectDir, { recursive: true, mode: 448 });
36072
+ const composeFile = path9.resolve(projectDir, "docker-compose.yml");
36073
+ const instancesFile = path9.resolve(projectDir, "instances.yml");
36074
+ if (!fs11.existsSync(projectDir)) {
36075
+ fs11.mkdirSync(projectDir, { recursive: true, mode: 448 });
34253
36076
  }
34254
- if (!fs9.existsSync(composeFile)) {
36077
+ if (!fs11.existsSync(composeFile)) {
34255
36078
  const refs = [
34256
36079
  process.env.PGAI_PROJECT_REF,
34257
36080
  package_default.version,
@@ -34263,39 +36086,39 @@ async function ensureDefaultMonitoringProject() {
34263
36086
  const url = `https://gitlab.com/postgres-ai/postgres_ai/-/raw/${encodeURIComponent(ref)}/docker-compose.yml`;
34264
36087
  try {
34265
36088
  const text = await downloadText(url);
34266
- fs9.writeFileSync(composeFile, text, { encoding: "utf8", mode: 384 });
36089
+ fs11.writeFileSync(composeFile, text, { encoding: "utf8", mode: 384 });
34267
36090
  break;
34268
36091
  } catch (err) {
34269
36092
  lastErr = err;
34270
36093
  }
34271
36094
  }
34272
- if (!fs9.existsSync(composeFile)) {
36095
+ if (!fs11.existsSync(composeFile)) {
34273
36096
  const msg = lastErr instanceof Error ? lastErr.message : String(lastErr);
34274
36097
  throw new Error(`Failed to bootstrap docker-compose.yml: ${msg}`);
34275
36098
  }
34276
36099
  }
34277
- if (fs9.existsSync(instancesFile) && fs9.lstatSync(instancesFile).isDirectory()) {
34278
- fs9.rmSync(instancesFile, { recursive: true, force: true });
36100
+ if (fs11.existsSync(instancesFile) && fs11.lstatSync(instancesFile).isDirectory()) {
36101
+ fs11.rmSync(instancesFile, { recursive: true, force: true });
34279
36102
  }
34280
- if (!fs9.existsSync(instancesFile)) {
36103
+ if (!fs11.existsSync(instancesFile)) {
34281
36104
  const header = `# PostgreSQL instances to monitor
34282
36105
  ` + `# Add your instances using: pgai mon targets add <connection-string> <name>
34283
36106
 
34284
36107
  `;
34285
- fs9.writeFileSync(instancesFile, header, { encoding: "utf8", mode: 384 });
36108
+ fs11.writeFileSync(instancesFile, header, { encoding: "utf8", mode: 384 });
34286
36109
  }
34287
- const pgwatchConfig = path7.resolve(projectDir, ".pgwatch-config");
34288
- if (!fs9.existsSync(pgwatchConfig)) {
34289
- fs9.writeFileSync(pgwatchConfig, "", { encoding: "utf8", mode: 384 });
36110
+ const pgwatchConfig = path9.resolve(projectDir, ".pgwatch-config");
36111
+ if (!fs11.existsSync(pgwatchConfig)) {
36112
+ fs11.writeFileSync(pgwatchConfig, "", { encoding: "utf8", mode: 384 });
34290
36113
  }
34291
- const envFile = path7.resolve(projectDir, ".env");
34292
- if (!fs9.existsSync(envFile)) {
36114
+ const envFile = path9.resolve(projectDir, ".env");
36115
+ if (!fs11.existsSync(envFile)) {
34293
36116
  const envText = `PGAI_TAG=${package_default.version}
34294
36117
  # PGAI_REGISTRY=registry.gitlab.com/postgres-ai/postgres_ai
34295
36118
  `;
34296
- fs9.writeFileSync(envFile, envText, { encoding: "utf8", mode: 384 });
36119
+ fs11.writeFileSync(envFile, envText, { encoding: "utf8", mode: 384 });
34297
36120
  }
34298
- return { fs: fs9, path: path7, projectDir, composeFile, instancesFile };
36121
+ return { fs: fs11, path: path9, projectDir, composeFile, instancesFile };
34299
36122
  }
34300
36123
  function sanitizeTagForBackup(tag) {
34301
36124
  if (tag == null)
@@ -34304,10 +36127,10 @@ function sanitizeTagForBackup(tag) {
34304
36127
  return /^[A-Za-z0-9._-]{1,64}$/.test(stripped) ? stripped : null;
34305
36128
  }
34306
36129
  function readDeployedTag(projectDir) {
34307
- const envFile = path7.resolve(projectDir, ".env");
34308
- if (!fs9.existsSync(envFile))
36130
+ const envFile = path9.resolve(projectDir, ".env");
36131
+ if (!fs11.existsSync(envFile))
34309
36132
  return null;
34310
- const m = fs9.readFileSync(envFile, "utf8").match(/^PGAI_TAG=(.+)$/m);
36133
+ const m = fs11.readFileSync(envFile, "utf8").match(/^PGAI_TAG=(.+)$/m);
34311
36134
  return m ? sanitizeTagForBackup(m[1]) : null;
34312
36135
  }
34313
36136
  function isValidComposeYaml(text) {
@@ -34344,10 +36167,10 @@ function composeContentEqual(a, b) {
34344
36167
  return a.replace(/\s+$/, "") === b.replace(/\s+$/, "");
34345
36168
  }
34346
36169
  async function refreshBundledComposeIfStale(projectDir, oldTag) {
34347
- if (fs9.existsSync(path7.resolve(projectDir, ".git")))
36170
+ if (fs11.existsSync(path9.resolve(projectDir, ".git")))
34348
36171
  return false;
34349
- const composeFile = path7.resolve(projectDir, "docker-compose.yml");
34350
- if (!fs9.existsSync(composeFile))
36172
+ const composeFile = path9.resolve(projectDir, "docker-compose.yml");
36173
+ if (!fs11.existsSync(composeFile))
34351
36174
  return false;
34352
36175
  const refs = [
34353
36176
  process.env.PGAI_PROJECT_REF,
@@ -34360,24 +36183,24 @@ async function refreshBundledComposeIfStale(projectDir, oldTag) {
34360
36183
  console.error(" Keeping the existing compose. If dashboards are blank after upgrade, re-run this command once network is available.");
34361
36184
  return false;
34362
36185
  }
34363
- const existing = fs9.readFileSync(composeFile, "utf8");
36186
+ const existing = fs11.readFileSync(composeFile, "utf8");
34364
36187
  if (composeContentEqual(existing, fetched))
34365
36188
  return false;
34366
36189
  const deployedTag = sanitizeTagForBackup(oldTag ?? readDeployedTag(projectDir));
34367
36190
  const tagPart = deployedTag ?? new Date().toISOString().replace(/[:.]/g, "-");
34368
- const oldHash = crypto2.createHash("sha256").update(existing).digest("hex").slice(0, 8);
34369
- const backup = path7.resolve(projectDir, `docker-compose.yml.bak-${tagPart}-${oldHash}`);
36191
+ const oldHash = crypto4.createHash("sha256").update(existing).digest("hex").slice(0, 8);
36192
+ const backup = path9.resolve(projectDir, `docker-compose.yml.bak-${tagPart}-${oldHash}`);
34370
36193
  let backupName = null;
34371
36194
  try {
34372
- fs9.writeFileSync(backup, existing, { encoding: "utf8", mode: 384, flag: "wx" });
34373
- backupName = path7.basename(backup);
36195
+ fs11.writeFileSync(backup, existing, { encoding: "utf8", mode: 384, flag: "wx" });
36196
+ backupName = path9.basename(backup);
34374
36197
  } catch (err) {
34375
36198
  const e = err;
34376
36199
  if (e && e.code === "EEXIST") {
34377
- backupName = path7.basename(backup);
36200
+ backupName = path9.basename(backup);
34378
36201
  }
34379
36202
  }
34380
- fs9.writeFileSync(composeFile, fetched, { encoding: "utf8", mode: 384 });
36203
+ fs11.writeFileSync(composeFile, fetched, { encoding: "utf8", mode: 384 });
34381
36204
  const backupNote = backupName ? ` (backup: ${backupName})` : "";
34382
36205
  console.log(`\u2713 Refreshed docker-compose.yml to ${package_default.version}${backupNote}`);
34383
36206
  return true;
@@ -35611,12 +37434,12 @@ function resolvePaths() {
35611
37434
  const startDir = process.cwd();
35612
37435
  let currentDir = startDir;
35613
37436
  while (true) {
35614
- const composeFile = path7.resolve(currentDir, "docker-compose.yml");
35615
- if (fs9.existsSync(composeFile)) {
35616
- const instancesFile = path7.resolve(currentDir, "instances.yml");
35617
- return { fs: fs9, path: path7, projectDir: currentDir, composeFile, instancesFile };
37437
+ const composeFile = path9.resolve(currentDir, "docker-compose.yml");
37438
+ if (fs11.existsSync(composeFile)) {
37439
+ const instancesFile = path9.resolve(currentDir, "instances.yml");
37440
+ return { fs: fs11, path: path9, projectDir: currentDir, composeFile, instancesFile };
35618
37441
  }
35619
- const parentDir = path7.dirname(currentDir);
37442
+ const parentDir = path9.dirname(currentDir);
35620
37443
  if (parentDir === currentDir)
35621
37444
  break;
35622
37445
  currentDir = parentDir;
@@ -35748,10 +37571,10 @@ function resolveAdoptedProject(reg) {
35748
37571
  }
35749
37572
  function updatePgwatchConfig(configPath, updates) {
35750
37573
  let lines = [];
35751
- if (fs9.existsSync(configPath)) {
35752
- const stats = fs9.statSync(configPath);
37574
+ if (fs11.existsSync(configPath)) {
37575
+ const stats = fs11.statSync(configPath);
35753
37576
  if (!stats.isDirectory()) {
35754
- const content = fs9.readFileSync(configPath, "utf8");
37577
+ const content = fs11.readFileSync(configPath, "utf8");
35755
37578
  lines = content.split(/\r?\n/).filter((l) => l.trim() !== "");
35756
37579
  }
35757
37580
  }
@@ -35763,7 +37586,7 @@ function updatePgwatchConfig(configPath, updates) {
35763
37586
  lines.push(`${key}=${value}`);
35764
37587
  }
35765
37588
  }
35766
- fs9.writeFileSync(configPath, lines.join(`
37589
+ fs11.writeFileSync(configPath, lines.join(`
35767
37590
  `) + `
35768
37591
  `, { encoding: "utf8", mode: 384 });
35769
37592
  }
@@ -35801,12 +37624,12 @@ async function runCompose(args, grafanaPassword) {
35801
37624
  if (grafanaPassword) {
35802
37625
  env.GF_SECURITY_ADMIN_PASSWORD = grafanaPassword;
35803
37626
  } else {
35804
- const cfgPath = path7.resolve(projectDir, ".pgwatch-config");
35805
- if (fs9.existsSync(cfgPath)) {
37627
+ const cfgPath = path9.resolve(projectDir, ".pgwatch-config");
37628
+ if (fs11.existsSync(cfgPath)) {
35806
37629
  try {
35807
- const stats = fs9.statSync(cfgPath);
37630
+ const stats = fs11.statSync(cfgPath);
35808
37631
  if (!stats.isDirectory()) {
35809
- const content = fs9.readFileSync(cfgPath, "utf8");
37632
+ const content = fs11.readFileSync(cfgPath, "utf8");
35810
37633
  const match = content.match(/^grafana_password=([^\r\n]+)/m);
35811
37634
  if (match) {
35812
37635
  env.GF_SECURITY_ADMIN_PASSWORD = match[1].trim();
@@ -35819,10 +37642,10 @@ async function runCompose(args, grafanaPassword) {
35819
37642
  }
35820
37643
  }
35821
37644
  }
35822
- const envFilePath = path7.resolve(projectDir, ".env");
35823
- if (fs9.existsSync(envFilePath)) {
37645
+ const envFilePath = path9.resolve(projectDir, ".env");
37646
+ if (fs11.existsSync(envFilePath)) {
35824
37647
  try {
35825
- const envContent = fs9.readFileSync(envFilePath, "utf8");
37648
+ const envContent = fs11.readFileSync(envFilePath, "utf8");
35826
37649
  if (!env.VM_AUTH_USERNAME) {
35827
37650
  const m = envContent.match(/^VM_AUTH_USERNAME=([^\r\n]+)/m);
35828
37651
  if (m)
@@ -35879,20 +37702,20 @@ mon.command("local-install").description("install local monitoring stack (genera
35879
37702
  console.log(`Project directory: ${projectDir}
35880
37703
  `);
35881
37704
  if (opts.project) {
35882
- const cfgPath2 = path7.resolve(projectDir, ".pgwatch-config");
37705
+ const cfgPath2 = path9.resolve(projectDir, ".pgwatch-config");
35883
37706
  updatePgwatchConfig(cfgPath2, { project_name: opts.project });
35884
37707
  console.log(`Using project name: ${opts.project}
35885
37708
  `);
35886
37709
  }
35887
- const envFile = path7.resolve(projectDir, ".env");
37710
+ const envFile = path9.resolve(projectDir, ".env");
35888
37711
  let existingRegistry = null;
35889
37712
  let existingPassword = null;
35890
37713
  let existingReplicatorPassword = null;
35891
37714
  let existingVmAuthUsername = null;
35892
37715
  let existingVmAuthPassword = null;
35893
37716
  let previousTag = null;
35894
- if (fs9.existsSync(envFile)) {
35895
- const existingEnv = fs9.readFileSync(envFile, "utf8");
37717
+ if (fs11.existsSync(envFile)) {
37718
+ const existingEnv = fs11.readFileSync(envFile, "utf8");
35896
37719
  const previousTagMatch = existingEnv.match(/^PGAI_TAG=(.+)$/m);
35897
37720
  if (previousTagMatch)
35898
37721
  previousTag = previousTagMatch[1].trim();
@@ -35920,10 +37743,10 @@ mon.command("local-install").description("install local monitoring stack (genera
35920
37743
  if (existingPassword) {
35921
37744
  envLines.push(`GF_SECURITY_ADMIN_PASSWORD=${existingPassword}`);
35922
37745
  }
35923
- envLines.push(`REPLICATOR_PASSWORD=${existingReplicatorPassword || crypto2.randomBytes(32).toString("hex")}`);
37746
+ envLines.push(`REPLICATOR_PASSWORD=${existingReplicatorPassword || crypto4.randomBytes(32).toString("hex")}`);
35924
37747
  envLines.push(`VM_AUTH_USERNAME=${existingVmAuthUsername || "vmauth"}`);
35925
- envLines.push(`VM_AUTH_PASSWORD=${existingVmAuthPassword || crypto2.randomBytes(18).toString("base64")}`);
35926
- fs9.writeFileSync(envFile, envLines.join(`
37748
+ envLines.push(`VM_AUTH_PASSWORD=${existingVmAuthPassword || crypto4.randomBytes(18).toString("base64")}`);
37749
+ fs11.writeFileSync(envFile, envLines.join(`
35927
37750
  `) + `
35928
37751
  `, { encoding: "utf8", mode: 384 });
35929
37752
  await refreshBundledComposeIfStale(projectDir, previousTag);
@@ -35960,7 +37783,7 @@ Use demo mode without API key: postgres-ai mon local-install --demo`);
35960
37783
  if (apiKey) {
35961
37784
  console.log("Using API key provided via --api-key parameter");
35962
37785
  writeConfig({ apiKey });
35963
- updatePgwatchConfig(path7.resolve(projectDir, ".pgwatch-config"), { api_key: apiKey });
37786
+ updatePgwatchConfig(path9.resolve(projectDir, ".pgwatch-config"), { api_key: apiKey });
35964
37787
  console.log(`\u2713 API key saved
35965
37788
  `);
35966
37789
  } else if (opts.yes) {
@@ -35977,7 +37800,7 @@ Use demo mode without API key: postgres-ai mon local-install --demo`);
35977
37800
  const trimmedKey = inputApiKey.trim();
35978
37801
  if (trimmedKey) {
35979
37802
  writeConfig({ apiKey: trimmedKey });
35980
- updatePgwatchConfig(path7.resolve(projectDir, ".pgwatch-config"), { api_key: trimmedKey });
37803
+ updatePgwatchConfig(path9.resolve(projectDir, ".pgwatch-config"), { api_key: trimmedKey });
35981
37804
  apiKey = trimmedKey;
35982
37805
  console.log(`\u2713 API key saved
35983
37806
  `);
@@ -36011,7 +37834,7 @@ Use demo mode without API key: postgres-ai mon local-install --demo`);
36011
37834
  # Add your instances using: postgres-ai mon targets add
36012
37835
 
36013
37836
  `;
36014
- fs9.writeFileSync(instancesPath2, emptyInstancesContent, "utf8");
37837
+ fs11.writeFileSync(instancesPath2, emptyInstancesContent, "utf8");
36015
37838
  console.log(`Instances file: ${instancesPath2}`);
36016
37839
  console.log(`Project directory: ${projectDir2}
36017
37840
  `);
@@ -36115,17 +37938,17 @@ You can provide either:`);
36115
37938
  }
36116
37939
  } else {
36117
37940
  console.log("Step 2: Demo mode enabled - using included demo PostgreSQL database");
36118
- const currentDir = path7.dirname(fileURLToPath2(import.meta.url));
37941
+ const currentDir = path9.dirname(fileURLToPath2(import.meta.url));
36119
37942
  const demoCandidates = [
36120
- path7.resolve(currentDir, "..", "..", "instances.demo.yml"),
36121
- path7.resolve(currentDir, "..", "..", "..", "instances.demo.yml")
37943
+ path9.resolve(currentDir, "..", "..", "instances.demo.yml"),
37944
+ path9.resolve(currentDir, "..", "..", "..", "instances.demo.yml")
36122
37945
  ];
36123
- const demoSrc = demoCandidates.find((p) => fs9.existsSync(p));
37946
+ const demoSrc = demoCandidates.find((p) => fs11.existsSync(p));
36124
37947
  if (demoSrc) {
36125
- if (fs9.existsSync(instancesPath) && fs9.lstatSync(instancesPath).isDirectory()) {
36126
- fs9.rmSync(instancesPath, { recursive: true, force: true });
37948
+ if (fs11.existsSync(instancesPath) && fs11.lstatSync(instancesPath).isDirectory()) {
37949
+ fs11.rmSync(instancesPath, { recursive: true, force: true });
36127
37950
  }
36128
- fs9.copyFileSync(demoSrc, instancesPath);
37951
+ fs11.copyFileSync(demoSrc, instancesPath);
36129
37952
  console.log(`\u2713 Demo monitoring target configured
36130
37953
  `);
36131
37954
  } else {
@@ -36144,15 +37967,15 @@ Searched: ${demoCandidates.join(", ")}
36144
37967
  console.log(`\u2713 Configuration updated
36145
37968
  `);
36146
37969
  console.log(opts.demo ? "Step 4: Configuring Grafana security..." : "Step 4: Configuring Grafana security...");
36147
- const cfgPath = path7.resolve(projectDir, ".pgwatch-config");
37970
+ const cfgPath = path9.resolve(projectDir, ".pgwatch-config");
36148
37971
  let grafanaPassword = "";
36149
37972
  let vmAuthUsername = "";
36150
37973
  let vmAuthPassword = "";
36151
37974
  try {
36152
- if (fs9.existsSync(cfgPath)) {
36153
- const stats = fs9.statSync(cfgPath);
37975
+ if (fs11.existsSync(cfgPath)) {
37976
+ const stats = fs11.statSync(cfgPath);
36154
37977
  if (!stats.isDirectory()) {
36155
- const content = fs9.readFileSync(cfgPath, "utf8");
37978
+ const content = fs11.readFileSync(cfgPath, "utf8");
36156
37979
  const match = content.match(/^grafana_password=([^\r\n]+)/m);
36157
37980
  if (match) {
36158
37981
  grafanaPassword = match[1].trim();
@@ -36164,15 +37987,15 @@ Searched: ${demoCandidates.join(", ")}
36164
37987
  const { stdout: password } = await execFilePromise("openssl", ["rand", "-base64", "12"]);
36165
37988
  grafanaPassword = password.trim().replace(/\n/g, "");
36166
37989
  let configContent = "";
36167
- if (fs9.existsSync(cfgPath)) {
36168
- const stats = fs9.statSync(cfgPath);
37990
+ if (fs11.existsSync(cfgPath)) {
37991
+ const stats = fs11.statSync(cfgPath);
36169
37992
  if (!stats.isDirectory()) {
36170
- configContent = fs9.readFileSync(cfgPath, "utf8");
37993
+ configContent = fs11.readFileSync(cfgPath, "utf8");
36171
37994
  }
36172
37995
  }
36173
37996
  const lines = configContent.split(/\r?\n/).filter((l) => !/^grafana_password=/.test(l));
36174
37997
  lines.push(`grafana_password=${grafanaPassword}`);
36175
- fs9.writeFileSync(cfgPath, lines.filter(Boolean).join(`
37998
+ fs11.writeFileSync(cfgPath, lines.filter(Boolean).join(`
36176
37999
  `) + `
36177
38000
  `, "utf8");
36178
38001
  }
@@ -36185,9 +38008,9 @@ Searched: ${demoCandidates.join(", ")}
36185
38008
  grafanaPassword = "demo";
36186
38009
  }
36187
38010
  try {
36188
- const envFile2 = path7.resolve(projectDir, ".env");
36189
- if (fs9.existsSync(envFile2)) {
36190
- const envContent = fs9.readFileSync(envFile2, "utf8");
38011
+ const envFile2 = path9.resolve(projectDir, ".env");
38012
+ if (fs11.existsSync(envFile2)) {
38013
+ const envContent = fs11.readFileSync(envFile2, "utf8");
36191
38014
  const userMatch = envContent.match(/^VM_AUTH_USERNAME=([^\r\n]+)/m);
36192
38015
  const passMatch = envContent.match(/^VM_AUTH_PASSWORD=([^\r\n]+)/m);
36193
38016
  if (userMatch)
@@ -36203,13 +38026,13 @@ Searched: ${demoCandidates.join(", ")}
36203
38026
  vmAuthPassword = vmPass.trim().replace(/\n/g, "");
36204
38027
  }
36205
38028
  let envContent = "";
36206
- if (fs9.existsSync(envFile2)) {
36207
- envContent = fs9.readFileSync(envFile2, "utf8");
38029
+ if (fs11.existsSync(envFile2)) {
38030
+ envContent = fs11.readFileSync(envFile2, "utf8");
36208
38031
  }
36209
38032
  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 === ""));
36210
38033
  envLines2.push(`VM_AUTH_USERNAME=${vmAuthUsername}`);
36211
38034
  envLines2.push(`VM_AUTH_PASSWORD=${vmAuthPassword}`);
36212
- fs9.writeFileSync(envFile2, envLines2.join(`
38035
+ fs11.writeFileSync(envFile2, envLines2.join(`
36213
38036
  `) + `
36214
38037
  `, { encoding: "utf8", mode: 384 });
36215
38038
  }
@@ -36242,7 +38065,7 @@ Searched: ${demoCandidates.join(", ")}
36242
38065
  });
36243
38066
  const adoptedProject = resolveAdoptedProject(reg);
36244
38067
  if (adoptedProject != null) {
36245
- updatePgwatchConfig(path7.resolve(projectDir, ".pgwatch-config"), {
38068
+ updatePgwatchConfig(path9.resolve(projectDir, ".pgwatch-config"), {
36246
38069
  project_name: adoptedProject
36247
38070
  });
36248
38071
  const verb = reg?.created ? "Registered" : "Adopted";
@@ -36458,11 +38281,11 @@ mon.command("config").description("show monitoring services configuration").acti
36458
38281
  console.log(`Project Directory: ${projectDir}`);
36459
38282
  console.log(`Docker Compose File: ${composeFile}`);
36460
38283
  console.log(`Instances File: ${instancesFile}`);
36461
- if (fs9.existsSync(instancesFile) && !fs9.lstatSync(instancesFile).isDirectory()) {
38284
+ if (fs11.existsSync(instancesFile) && !fs11.lstatSync(instancesFile).isDirectory()) {
36462
38285
  console.log(`
36463
38286
  Instances configuration:
36464
38287
  `);
36465
- const text = fs9.readFileSync(instancesFile, "utf8");
38288
+ const text = fs11.readFileSync(instancesFile, "utf8");
36466
38289
  process.stdout.write(text);
36467
38290
  if (!/\n$/.test(text))
36468
38291
  console.log();
@@ -36511,8 +38334,8 @@ mon.command("update").description("update monitoring stack (migrate .env, pull i
36511
38334
  console.log("\u2713 .env is up to date");
36512
38335
  }
36513
38336
  console.log();
36514
- const gitDir = path7.resolve(projectDir, ".git");
36515
- if (fs9.existsSync(gitDir)) {
38337
+ const gitDir = path9.resolve(projectDir, ".git");
38338
+ if (fs11.existsSync(gitDir)) {
36516
38339
  console.log("Fetching latest changes...");
36517
38340
  await execFilePromise("git", ["fetch", "origin"]);
36518
38341
  const { stdout: branch } = await execFilePromise("git", ["rev-parse", "--abbrev-ref", "HEAD"]);
@@ -36667,7 +38490,7 @@ mon.command("check").description("monitoring services system readiness check").a
36667
38490
  var targets = mon.command("targets").description("manage databases to monitor");
36668
38491
  targets.command("list").description("list monitoring target databases").action(async () => {
36669
38492
  const { instancesFile: instancesPath, projectDir } = await resolveOrInitPaths();
36670
- if (!fs9.existsSync(instancesPath) || fs9.lstatSync(instancesPath).isDirectory()) {
38493
+ if (!fs11.existsSync(instancesPath) || fs11.lstatSync(instancesPath).isDirectory()) {
36671
38494
  console.error(`instances.yml not found in ${projectDir}`);
36672
38495
  process.exitCode = 1;
36673
38496
  return;
@@ -36730,7 +38553,7 @@ targets.command("add [connStr] [name]").description("add monitoring target datab
36730
38553
  });
36731
38554
  targets.command("remove <name>").description("remove monitoring target database").action(async (name) => {
36732
38555
  const { instancesFile: file } = await resolveOrInitPaths();
36733
- if (!fs9.existsSync(file) || fs9.lstatSync(file).isDirectory()) {
38556
+ if (!fs11.existsSync(file) || fs11.lstatSync(file).isDirectory()) {
36734
38557
  console.error("instances.yml not found");
36735
38558
  process.exitCode = 1;
36736
38559
  return;
@@ -36758,7 +38581,7 @@ targets.command("remove <name>").description("remove monitoring target database"
36758
38581
  });
36759
38582
  targets.command("test <name>").description("test monitoring target database connectivity").action(async (name) => {
36760
38583
  const { instancesFile: instancesPath } = await resolveOrInitPaths();
36761
- if (!fs9.existsSync(instancesPath) || fs9.lstatSync(instancesPath).isDirectory()) {
38584
+ if (!fs11.existsSync(instancesPath) || fs11.lstatSync(instancesPath).isDirectory()) {
36762
38585
  console.error("instances.yml not found");
36763
38586
  process.exitCode = 1;
36764
38587
  return;
@@ -37020,15 +38843,15 @@ To authenticate, run: pgai auth`);
37020
38843
  });
37021
38844
  auth.command("remove-key").description("remove API key").action(async () => {
37022
38845
  const newConfigPath = getConfigPath();
37023
- const hasNewConfig = fs9.existsSync(newConfigPath);
38846
+ const hasNewConfig = fs11.existsSync(newConfigPath);
37024
38847
  let legacyPath;
37025
38848
  try {
37026
38849
  const { projectDir } = await resolveOrInitPaths();
37027
- legacyPath = path7.resolve(projectDir, ".pgwatch-config");
38850
+ legacyPath = path9.resolve(projectDir, ".pgwatch-config");
37028
38851
  } catch {
37029
- legacyPath = path7.resolve(process.cwd(), ".pgwatch-config");
38852
+ legacyPath = path9.resolve(process.cwd(), ".pgwatch-config");
37030
38853
  }
37031
- const hasLegacyConfig = fs9.existsSync(legacyPath) && fs9.statSync(legacyPath).isFile();
38854
+ const hasLegacyConfig = fs11.existsSync(legacyPath) && fs11.statSync(legacyPath).isFile();
37032
38855
  if (!hasNewConfig && !hasLegacyConfig) {
37033
38856
  console.log("No API key configured");
37034
38857
  return;
@@ -37038,11 +38861,11 @@ auth.command("remove-key").description("remove API key").action(async () => {
37038
38861
  }
37039
38862
  if (hasLegacyConfig) {
37040
38863
  try {
37041
- const content = fs9.readFileSync(legacyPath, "utf8");
38864
+ const content = fs11.readFileSync(legacyPath, "utf8");
37042
38865
  const filtered = content.split(/\r?\n/).filter((l) => !/^api_key=/.test(l)).join(`
37043
38866
  `).replace(/\n+$/g, `
37044
38867
  `);
37045
- fs9.writeFileSync(legacyPath, filtered, "utf8");
38868
+ fs11.writeFileSync(legacyPath, filtered, "utf8");
37046
38869
  } catch (err) {
37047
38870
  console.warn(`Warning: Could not update legacy config: ${err instanceof Error ? err.message : String(err)}`);
37048
38871
  }
@@ -37053,7 +38876,7 @@ To authenticate again, run: pgai auth`);
37053
38876
  });
37054
38877
  mon.command("generate-grafana-password").description("generate Grafana password for monitoring services").action(async () => {
37055
38878
  const { projectDir } = await resolveOrInitPaths();
37056
- const cfgPath = path7.resolve(projectDir, ".pgwatch-config");
38879
+ const cfgPath = path9.resolve(projectDir, ".pgwatch-config");
37057
38880
  try {
37058
38881
  const { stdout: password } = await execFilePromise("openssl", ["rand", "-base64", "12"]);
37059
38882
  const newPassword = password.trim().replace(/\n/g, "");
@@ -37063,17 +38886,17 @@ mon.command("generate-grafana-password").description("generate Grafana password
37063
38886
  return;
37064
38887
  }
37065
38888
  let configContent = "";
37066
- if (fs9.existsSync(cfgPath)) {
37067
- const stats = fs9.statSync(cfgPath);
38889
+ if (fs11.existsSync(cfgPath)) {
38890
+ const stats = fs11.statSync(cfgPath);
37068
38891
  if (stats.isDirectory()) {
37069
38892
  console.error(".pgwatch-config is a directory, expected a file. Skipping read.");
37070
38893
  } else {
37071
- configContent = fs9.readFileSync(cfgPath, "utf8");
38894
+ configContent = fs11.readFileSync(cfgPath, "utf8");
37072
38895
  }
37073
38896
  }
37074
38897
  const lines = configContent.split(/\r?\n/).filter((l) => !/^grafana_password=/.test(l));
37075
38898
  lines.push(`grafana_password=${newPassword}`);
37076
- fs9.writeFileSync(cfgPath, lines.filter(Boolean).join(`
38899
+ fs11.writeFileSync(cfgPath, lines.filter(Boolean).join(`
37077
38900
  `) + `
37078
38901
  `, "utf8");
37079
38902
  console.log("\u2713 New Grafana password generated and saved");
@@ -37095,19 +38918,19 @@ Note: This command requires 'openssl' to be installed`);
37095
38918
  });
37096
38919
  mon.command("show-grafana-credentials").description("show Grafana credentials for monitoring services").action(async () => {
37097
38920
  const { projectDir } = await resolveOrInitPaths();
37098
- const cfgPath = path7.resolve(projectDir, ".pgwatch-config");
37099
- if (!fs9.existsSync(cfgPath)) {
38921
+ const cfgPath = path9.resolve(projectDir, ".pgwatch-config");
38922
+ if (!fs11.existsSync(cfgPath)) {
37100
38923
  console.error("Configuration file not found. Run 'postgres-ai mon local-install' first.");
37101
38924
  process.exitCode = 1;
37102
38925
  return;
37103
38926
  }
37104
- const stats = fs9.statSync(cfgPath);
38927
+ const stats = fs11.statSync(cfgPath);
37105
38928
  if (stats.isDirectory()) {
37106
38929
  console.error(".pgwatch-config is a directory, expected a file. Cannot read credentials.");
37107
38930
  process.exitCode = 1;
37108
38931
  return;
37109
38932
  }
37110
- const content = fs9.readFileSync(cfgPath, "utf8");
38933
+ const content = fs11.readFileSync(cfgPath, "utf8");
37111
38934
  const lines = content.split(/\r?\n/);
37112
38935
  let password = "";
37113
38936
  for (const line of lines) {
@@ -37127,9 +38950,9 @@ Grafana credentials:`);
37127
38950
  console.log(" URL: http://localhost:3000");
37128
38951
  console.log(" Username: monitor");
37129
38952
  console.log(` Password: ${password}`);
37130
- const envFile = path7.resolve(projectDir, ".env");
37131
- if (fs9.existsSync(envFile)) {
37132
- const envContent = fs9.readFileSync(envFile, "utf8");
38953
+ const envFile = path9.resolve(projectDir, ".env");
38954
+ if (fs11.existsSync(envFile)) {
38955
+ const envContent = fs11.readFileSync(envFile, "utf8");
37133
38956
  const vmUser = envContent.match(/^VM_AUTH_USERNAME=([^\r\n]+)/m);
37134
38957
  const vmPass = envContent.match(/^VM_AUTH_PASSWORD=([^\r\n]+)/m);
37135
38958
  if (vmUser && vmPass) {
@@ -37855,13 +39678,13 @@ reports.command("data [reportId]").description("get checkup report file data (in
37855
39678
  });
37856
39679
  spinner.stop();
37857
39680
  if (opts.output) {
37858
- const dir = path7.resolve(opts.output);
37859
- fs9.mkdirSync(dir, { recursive: true });
39681
+ const dir = path9.resolve(opts.output);
39682
+ fs11.mkdirSync(dir, { recursive: true });
37860
39683
  for (const f of result) {
37861
- const safeName = path7.basename(f.filename);
37862
- const filePath = path7.join(dir, safeName);
39684
+ const safeName = path9.basename(f.filename);
39685
+ const filePath = path9.join(dir, safeName);
37863
39686
  const content = f.type === "json" ? JSON.stringify(tryParseJson(f.data), null, 2) : f.data;
37864
- fs9.writeFileSync(filePath, content, "utf-8");
39687
+ fs11.writeFileSync(filePath, content, "utf-8");
37865
39688
  console.log(filePath);
37866
39689
  }
37867
39690
  } else if (opts.json) {
@@ -37906,6 +39729,406 @@ function tryParseJson(s) {
37906
39729
  return s;
37907
39730
  }
37908
39731
  }
39732
+ function inferCommandFromResult(r) {
39733
+ if (r.command)
39734
+ return r.command;
39735
+ if (r.plan_text !== undefined || r.plan_json !== undefined)
39736
+ return "plan";
39737
+ if (r.row_count !== undefined || r.result_rows !== undefined)
39738
+ return "exec";
39739
+ if (r.hypo_used !== undefined)
39740
+ return "hypo";
39741
+ if (r.terminated !== undefined)
39742
+ return "terminate";
39743
+ if (r.reset !== undefined)
39744
+ return "reset";
39745
+ if (r.snapshot !== undefined)
39746
+ return "activity";
39747
+ return null;
39748
+ }
39749
+ function printJoeOutcome(command, outcome, json3) {
39750
+ const budgetSeconds = Math.round(DEFAULT_BUDGET_MS2 / 1000);
39751
+ if (outcome.budgetExpired) {
39752
+ if (json3) {
39753
+ console.log(JSON.stringify({
39754
+ command_id: outcome.commandId,
39755
+ status: outcome.status,
39756
+ session_id: outcome.sessionId,
39757
+ budget_expired: true,
39758
+ resume: `pgai result ${outcome.commandId}`
39759
+ }, null, 2));
39760
+ } else {
39761
+ console.log(`submitted ${outcome.commandId} \xB7 ${outcome.status} \xB7 budget ${budgetSeconds}s reached \u2014 resume: pgai result ${outcome.commandId}`);
39762
+ }
39763
+ return;
39764
+ }
39765
+ const result = outcome.result;
39766
+ if (outcome.status === "done" && result) {
39767
+ if (json3) {
39768
+ console.log(JSON.stringify(result, null, 2));
39769
+ } else {
39770
+ console.log(`submitted ${outcome.commandId} \xB7 done`);
39771
+ const body = formatJoeResult(command, result);
39772
+ if (body)
39773
+ console.log(body);
39774
+ }
39775
+ return;
39776
+ }
39777
+ if (outcome.status === "error") {
39778
+ const msg = result?.error ?? "command failed";
39779
+ console.error(`command ${outcome.commandId} error: ${msg}`);
39780
+ process.exitCode = 1;
39781
+ return;
39782
+ }
39783
+ console.error(`command ${outcome.commandId} timed out on the server \u2014 resume: pgai result ${outcome.commandId}`);
39784
+ process.exitCode = 1;
39785
+ }
39786
+ async function runJoeCli(command, sql, args, opts) {
39787
+ try {
39788
+ const rootOpts = program2.opts();
39789
+ const cfg = readConfig();
39790
+ const { apiKey } = getConfig(rootOpts);
39791
+ if (!apiKey) {
39792
+ console.error("API key is required. Run 'pgai auth' first or set --api-key.");
39793
+ process.exitCode = 1;
39794
+ return;
39795
+ }
39796
+ const projectRef = (opts.project ?? cfg.defaultProject ?? "").toString().trim();
39797
+ if (!projectRef) {
39798
+ console.error("Project is required. Pass --project <id|alias>.");
39799
+ process.exitCode = 1;
39800
+ return;
39801
+ }
39802
+ const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
39803
+ const budgetMs = typeof opts.budget === "number" && !Number.isNaN(opts.budget) ? opts.budget * 1000 : undefined;
39804
+ const outcome = await executeJoeCommand2({
39805
+ apiKey,
39806
+ apiBaseUrl,
39807
+ command,
39808
+ project: projectRef,
39809
+ sql,
39810
+ args,
39811
+ session: opts.session ?? null,
39812
+ newSession: !!opts.newSession,
39813
+ orgId: cfg.orgId ?? undefined,
39814
+ budgetMs,
39815
+ debug: !!opts.debug
39816
+ });
39817
+ printJoeOutcome(command, outcome, !!opts.json);
39818
+ } catch (err) {
39819
+ const message = err instanceof Error ? err.message : String(err);
39820
+ console.error(message);
39821
+ process.exitCode = 1;
39822
+ }
39823
+ }
39824
+ function withJoeOptions(cmd) {
39825
+ 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");
39826
+ }
39827
+ withJoeOptions(program2.command("plan <sql>").description("plan a query (EXPLAIN, plan-only \u2014 no execution; the fast/safe default)")).action(async (sql, opts) => {
39828
+ await runJoeCli("plan", sql, null, opts);
39829
+ });
39830
+ withJoeOptions(program2.command("explain <sql>").description("EXPLAIN ANALYZE a query (EXECUTES on the hardened, ephemeral clone)")).action(async (sql, opts) => {
39831
+ await runJoeCli("explain", sql, null, opts);
39832
+ });
39833
+ withJoeOptions(program2.command("exec <sql>").description("run arbitrary DDL/DML on the clone (e.g. create index, analyze)")).action(async (sql, opts) => {
39834
+ await runJoeCli("exec", sql, null, opts);
39835
+ });
39836
+ withJoeOptions(program2.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) => {
39837
+ await runJoeCli("hypo", indexSql, { query: opts.query }, opts);
39838
+ });
39839
+ withJoeOptions(program2.command("activity").description("running-activity snapshot (pg_stat_activity; query text redacted)")).action(async (opts) => {
39840
+ await runJoeCli("activity", null, null, opts);
39841
+ });
39842
+ withJoeOptions(program2.command("terminate <pid>").description("pg_terminate_backend(pid) on the clone")).action(async (pid, opts) => {
39843
+ const pidNum = parseInt(pid, 10);
39844
+ if (Number.isNaN(pidNum)) {
39845
+ console.error("pid must be a number");
39846
+ process.exitCode = 1;
39847
+ return;
39848
+ }
39849
+ await runJoeCli("terminate", null, { pid: pidNum }, opts);
39850
+ });
39851
+ withJoeOptions(program2.command("reset").description("reset/recreate the session's thin clone")).action(async (opts) => {
39852
+ await runJoeCli("reset", null, null, opts);
39853
+ });
39854
+ withJoeOptions(program2.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) => {
39855
+ const args = { object: object4 };
39856
+ if (opts.variant)
39857
+ args.variant = opts.variant;
39858
+ await runJoeCli("describe", null, args, opts);
39859
+ });
39860
+ 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) => {
39861
+ try {
39862
+ const rootOpts = program2.opts();
39863
+ const cfg = readConfig();
39864
+ const { apiKey } = getConfig(rootOpts);
39865
+ if (!apiKey) {
39866
+ console.error("API key is required. Run 'pgai auth' first or set --api-key.");
39867
+ process.exitCode = 1;
39868
+ return;
39869
+ }
39870
+ const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
39871
+ const projects = await listProjects2({
39872
+ apiKey,
39873
+ apiBaseUrl,
39874
+ orgId: cfg.orgId ?? undefined,
39875
+ debug: !!opts.debug
39876
+ });
39877
+ if (opts.json) {
39878
+ console.log(JSON.stringify(projects, null, 2));
39879
+ } else {
39880
+ console.log(formatProjectsTable(projects));
39881
+ }
39882
+ } catch (err) {
39883
+ const message = err instanceof Error ? err.message : String(err);
39884
+ console.error(message);
39885
+ process.exitCode = 1;
39886
+ }
39887
+ });
39888
+ async function resolveDblabTarget(project, debug) {
39889
+ const rootOpts = program2.opts();
39890
+ const cfg = readConfig();
39891
+ const { apiKey } = getConfig(rootOpts);
39892
+ if (!apiKey) {
39893
+ throw new Error("API key is required. Run 'pgai auth' first or set --api-key.");
39894
+ }
39895
+ const ref = (project ?? "").trim();
39896
+ if (!ref) {
39897
+ throw new Error("--project <id|alias> is required");
39898
+ }
39899
+ const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
39900
+ const orgId = cfg.orgId ?? undefined;
39901
+ const instanceId = await resolveDblabInstanceId2({ apiKey, apiBaseUrl, project: ref, orgId, debug });
39902
+ return { apiKey, apiBaseUrl, orgId, instanceId };
39903
+ }
39904
+ var clone2 = program2.command("clone").description("DBLab thin-clone management (proxies the Platform DBLab API)");
39905
+ 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) => {
39906
+ try {
39907
+ const { apiKey, apiBaseUrl, instanceId } = await resolveDblabTarget(opts.project, !!opts.debug);
39908
+ const result = await createClone2({
39909
+ apiKey,
39910
+ apiBaseUrl,
39911
+ instanceId,
39912
+ cloneId: opts.id,
39913
+ branch: opts.branch,
39914
+ snapshotId: opts.snapshot,
39915
+ dbUser: opts.dbUser,
39916
+ dbPassword: opts.dbPassword,
39917
+ isProtected: !!opts.protected,
39918
+ debug: !!opts.debug
39919
+ });
39920
+ printResult(result, opts.json);
39921
+ } catch (err) {
39922
+ console.error(err instanceof Error ? err.message : String(err));
39923
+ process.exitCode = 1;
39924
+ }
39925
+ });
39926
+ program2.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) => {
39927
+ try {
39928
+ const rootOpts = program2.opts();
39929
+ const cfg = readConfig();
39930
+ const { apiKey } = getConfig(rootOpts);
39931
+ if (!apiKey) {
39932
+ console.error("API key is required. Run 'pgai auth' first or set --api-key.");
39933
+ process.exitCode = 1;
39934
+ return;
39935
+ }
39936
+ const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
39937
+ const status = await getCommandStatus2({ apiKey, apiBaseUrl, commandId, debug: !!opts.debug });
39938
+ if (opts.json) {
39939
+ console.log(JSON.stringify(status, null, 2));
39940
+ } else {
39941
+ console.log(`command ${status.command_id} \xB7 ${status.status}${status.error ? ` \xB7 ${status.error}` : ""}`);
39942
+ }
39943
+ } catch (err) {
39944
+ const message = err instanceof Error ? err.message : String(err);
39945
+ console.error(message);
39946
+ process.exitCode = 1;
39947
+ }
39948
+ });
39949
+ 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) => {
39950
+ try {
39951
+ const { apiKey, apiBaseUrl, instanceId } = await resolveDblabTarget(opts.project, !!opts.debug);
39952
+ const result = await listClones2({ apiKey, apiBaseUrl, instanceId, debug: !!opts.debug });
39953
+ printResult(result, opts.json);
39954
+ } catch (err) {
39955
+ console.error(err instanceof Error ? err.message : String(err));
39956
+ process.exitCode = 1;
39957
+ }
39958
+ });
39959
+ program2.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) => {
39960
+ try {
39961
+ const rootOpts = program2.opts();
39962
+ const cfg = readConfig();
39963
+ const { apiKey } = getConfig(rootOpts);
39964
+ if (!apiKey) {
39965
+ console.error("API key is required. Run 'pgai auth' first or set --api-key.");
39966
+ process.exitCode = 1;
39967
+ return;
39968
+ }
39969
+ const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
39970
+ const result = await getCommandResult2({ apiKey, apiBaseUrl, commandId, debug: !!opts.debug });
39971
+ if (opts.json) {
39972
+ console.log(JSON.stringify(result, null, 2));
39973
+ return;
39974
+ }
39975
+ const inferred = inferCommandFromResult(result);
39976
+ if (result.status === "done" && inferred) {
39977
+ console.log(`command ${result.command_id} \xB7 done`);
39978
+ const body = formatJoeResult(inferred, result);
39979
+ if (body)
39980
+ console.log(body);
39981
+ } else if (result.status === "error") {
39982
+ console.error(`command ${result.command_id} error: ${result.error ?? "command failed"}`);
39983
+ process.exitCode = 1;
39984
+ } else {
39985
+ console.log(`command ${result.command_id} \xB7 ${result.status}`);
39986
+ }
39987
+ } catch (err) {
39988
+ const message = err instanceof Error ? err.message : String(err);
39989
+ console.error(message);
39990
+ process.exitCode = 1;
39991
+ }
39992
+ });
39993
+ 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) => {
39994
+ try {
39995
+ const { apiKey, apiBaseUrl, instanceId } = await resolveDblabTarget(opts.project, !!opts.debug);
39996
+ const result = await getClone2({ apiKey, apiBaseUrl, instanceId, cloneId, debug: !!opts.debug });
39997
+ printResult(result, opts.json);
39998
+ } catch (err) {
39999
+ console.error(err instanceof Error ? err.message : String(err));
40000
+ process.exitCode = 1;
40001
+ }
40002
+ });
40003
+ clone2.command("reset <cloneId>").description("reset a clone to a pristine snapshot (requires joe:admin)").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) => {
40004
+ try {
40005
+ const { apiKey, apiBaseUrl, instanceId } = await resolveDblabTarget(opts.project, !!opts.debug);
40006
+ const result = await resetClone2({
40007
+ apiKey,
40008
+ apiBaseUrl,
40009
+ instanceId,
40010
+ cloneId,
40011
+ snapshotId: opts.snapshot,
40012
+ latest: opts.latest,
40013
+ debug: !!opts.debug
40014
+ });
40015
+ printResult(result ?? { reset: true, cloneId }, opts.json);
40016
+ } catch (err) {
40017
+ console.error(err instanceof Error ? err.message : String(err));
40018
+ process.exitCode = 1;
40019
+ }
40020
+ });
40021
+ 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) => {
40022
+ try {
40023
+ const { apiKey, apiBaseUrl, instanceId } = await resolveDblabTarget(opts.project, !!opts.debug);
40024
+ const result = await destroyClone2({ apiKey, apiBaseUrl, instanceId, cloneId, debug: !!opts.debug });
40025
+ printResult(result ?? { destroyed: true, cloneId }, opts.json);
40026
+ } catch (err) {
40027
+ console.error(err instanceof Error ? err.message : String(err));
40028
+ process.exitCode = 1;
40029
+ }
40030
+ });
40031
+ var branch = program2.command("branch").description("DBLab branch management (proxies the Platform DBLab API)");
40032
+ 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) => {
40033
+ try {
40034
+ const { apiKey, apiBaseUrl, instanceId } = await resolveDblabTarget(opts.project, !!opts.debug);
40035
+ const result = await listBranches2({ apiKey, apiBaseUrl, instanceId, debug: !!opts.debug });
40036
+ printResult(result, opts.json);
40037
+ } catch (err) {
40038
+ console.error(err instanceof Error ? err.message : String(err));
40039
+ process.exitCode = 1;
40040
+ }
40041
+ });
40042
+ 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) => {
40043
+ try {
40044
+ const { apiKey, apiBaseUrl, instanceId } = await resolveDblabTarget(opts.project, !!opts.debug);
40045
+ const result = await createBranch2({
40046
+ apiKey,
40047
+ apiBaseUrl,
40048
+ instanceId,
40049
+ branchName: name,
40050
+ baseBranch: opts.baseBranch,
40051
+ snapshotId: opts.snapshot,
40052
+ debug: !!opts.debug
40053
+ });
40054
+ printResult(result, opts.json);
40055
+ } catch (err) {
40056
+ console.error(err instanceof Error ? err.message : String(err));
40057
+ process.exitCode = 1;
40058
+ }
40059
+ });
40060
+ 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) => {
40061
+ try {
40062
+ const { apiKey, apiBaseUrl, instanceId } = await resolveDblabTarget(opts.project, !!opts.debug);
40063
+ const result = await deleteBranch2({ apiKey, apiBaseUrl, instanceId, branchName: name, debug: !!opts.debug });
40064
+ printResult(result ?? { deleted: true, branch: name }, opts.json);
40065
+ } catch (err) {
40066
+ console.error(err instanceof Error ? err.message : String(err));
40067
+ process.exitCode = 1;
40068
+ }
40069
+ });
40070
+ 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) => {
40071
+ try {
40072
+ const { apiKey, apiBaseUrl, instanceId } = await resolveDblabTarget(opts.project, !!opts.debug);
40073
+ const result = await branchLog2({ apiKey, apiBaseUrl, instanceId, branchName: name, debug: !!opts.debug });
40074
+ printResult(result, opts.json);
40075
+ } catch (err) {
40076
+ console.error(err instanceof Error ? err.message : String(err));
40077
+ process.exitCode = 1;
40078
+ }
40079
+ });
40080
+ var snapshot = program2.command("snapshot").description("DBLab snapshot management (proxies the Platform DBLab API)");
40081
+ 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) => {
40082
+ try {
40083
+ const { apiKey, apiBaseUrl, instanceId } = await resolveDblabTarget(opts.project, !!opts.debug);
40084
+ const result = await listSnapshots2({
40085
+ apiKey,
40086
+ apiBaseUrl,
40087
+ instanceId,
40088
+ branchName: opts.branch,
40089
+ dataset: opts.dataset,
40090
+ debug: !!opts.debug
40091
+ });
40092
+ printResult(result, opts.json);
40093
+ } catch (err) {
40094
+ console.error(err instanceof Error ? err.message : String(err));
40095
+ process.exitCode = 1;
40096
+ }
40097
+ });
40098
+ 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) => {
40099
+ try {
40100
+ const { apiKey, apiBaseUrl, instanceId } = await resolveDblabTarget(opts.project, !!opts.debug);
40101
+ const result = await createSnapshot2({
40102
+ apiKey,
40103
+ apiBaseUrl,
40104
+ instanceId,
40105
+ cloneId: opts.clone,
40106
+ message: opts.message,
40107
+ debug: !!opts.debug
40108
+ });
40109
+ printResult(result, opts.json);
40110
+ } catch (err) {
40111
+ console.error(err instanceof Error ? err.message : String(err));
40112
+ process.exitCode = 1;
40113
+ }
40114
+ });
40115
+ 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) => {
40116
+ try {
40117
+ const { apiKey, apiBaseUrl, instanceId } = await resolveDblabTarget(opts.project, !!opts.debug);
40118
+ const result = await destroySnapshot2({
40119
+ apiKey,
40120
+ apiBaseUrl,
40121
+ instanceId,
40122
+ snapshotId,
40123
+ force: !!opts.force,
40124
+ debug: !!opts.debug
40125
+ });
40126
+ printResult(result ?? { destroyed: true, snapshot: snapshotId }, opts.json);
40127
+ } catch (err) {
40128
+ console.error(err instanceof Error ? err.message : String(err));
40129
+ process.exitCode = 1;
40130
+ }
40131
+ });
37909
40132
  var mcp = program2.command("mcp").description("MCP server integration");
37910
40133
  mcp.command("start").description("start MCP stdio server").option("--debug", "enable debug output").action(async (opts) => {
37911
40134
  const rootOpts = program2.opts();
@@ -37979,29 +40202,29 @@ mcp.command("install [client]").description("install MCP server configuration fo
37979
40202
  let configDir;
37980
40203
  switch (client) {
37981
40204
  case "cursor":
37982
- configPath = path7.join(homeDir, ".cursor", "mcp.json");
37983
- configDir = path7.dirname(configPath);
40205
+ configPath = path9.join(homeDir, ".cursor", "mcp.json");
40206
+ configDir = path9.dirname(configPath);
37984
40207
  break;
37985
40208
  case "windsurf":
37986
- configPath = path7.join(homeDir, ".windsurf", "mcp.json");
37987
- configDir = path7.dirname(configPath);
40209
+ configPath = path9.join(homeDir, ".windsurf", "mcp.json");
40210
+ configDir = path9.dirname(configPath);
37988
40211
  break;
37989
40212
  case "codex":
37990
- configPath = path7.join(homeDir, ".codex", "mcp.json");
37991
- configDir = path7.dirname(configPath);
40213
+ configPath = path9.join(homeDir, ".codex", "mcp.json");
40214
+ configDir = path9.dirname(configPath);
37992
40215
  break;
37993
40216
  default:
37994
40217
  console.error(`Configuration not implemented for: ${client}`);
37995
40218
  process.exitCode = 1;
37996
40219
  return;
37997
40220
  }
37998
- if (!fs9.existsSync(configDir)) {
37999
- fs9.mkdirSync(configDir, { recursive: true });
40221
+ if (!fs11.existsSync(configDir)) {
40222
+ fs11.mkdirSync(configDir, { recursive: true });
38000
40223
  }
38001
40224
  let config2 = { mcpServers: {} };
38002
- if (fs9.existsSync(configPath)) {
40225
+ if (fs11.existsSync(configPath)) {
38003
40226
  try {
38004
- const content = fs9.readFileSync(configPath, "utf8");
40227
+ const content = fs11.readFileSync(configPath, "utf8");
38005
40228
  config2 = JSON.parse(content);
38006
40229
  if (!config2.mcpServers) {
38007
40230
  config2.mcpServers = {};
@@ -38014,7 +40237,7 @@ mcp.command("install [client]").description("install MCP server configuration fo
38014
40237
  command: pgaiPath,
38015
40238
  args: ["mcp", "start"]
38016
40239
  };
38017
- fs9.writeFileSync(configPath, JSON.stringify(config2, null, 2), "utf8");
40240
+ fs11.writeFileSync(configPath, JSON.stringify(config2, null, 2), "utf8");
38018
40241
  console.log(`\u2713 PostgresAI MCP server configured for ${client}`);
38019
40242
  console.log(` Config file: ${configPath}`);
38020
40243
  console.log("");