postgresai 0.15.0-dev.1 → 0.15.0-dev.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,27 +6,46 @@ var __getProtoOf = Object.getPrototypeOf;
6
6
  var __defProp = Object.defineProperty;
7
7
  var __getOwnPropNames = Object.getOwnPropertyNames;
8
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ function __accessProp(key) {
10
+ return this[key];
11
+ }
12
+ var __toESMCache_node;
13
+ var __toESMCache_esm;
9
14
  var __toESM = (mod, isNodeMode, target) => {
15
+ var canCache = mod != null && typeof mod === "object";
16
+ if (canCache) {
17
+ var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
18
+ var cached = cache.get(mod);
19
+ if (cached)
20
+ return cached;
21
+ }
10
22
  target = mod != null ? __create(__getProtoOf(mod)) : {};
11
23
  const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
12
24
  for (let key of __getOwnPropNames(mod))
13
25
  if (!__hasOwnProp.call(to, key))
14
26
  __defProp(to, key, {
15
- get: () => mod[key],
27
+ get: __accessProp.bind(mod, key),
16
28
  enumerable: true
17
29
  });
30
+ if (canCache)
31
+ cache.set(mod, to);
18
32
  return to;
19
33
  };
20
34
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
35
+ var __returnValue = (v) => v;
36
+ function __exportSetter(name, newValue) {
37
+ this[name] = __returnValue.bind(null, newValue);
38
+ }
21
39
  var __export = (target, all) => {
22
40
  for (var name in all)
23
41
  __defProp(target, name, {
24
42
  get: all[name],
25
43
  enumerable: true,
26
44
  configurable: true,
27
- set: (newValue) => all[name] = () => newValue
45
+ set: __exportSetter.bind(all, name)
28
46
  });
29
47
  };
48
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
30
49
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
31
50
 
32
51
  // node_modules/commander/lib/error.js
@@ -79,7 +98,7 @@ var require_argument = __commonJS((exports) => {
79
98
  this._name = name;
80
99
  break;
81
100
  }
82
- if (this._name.length > 3 && this._name.slice(-3) === "...") {
101
+ if (this._name.endsWith("...")) {
83
102
  this.variadic = true;
84
103
  this._name = this._name.slice(0, -3);
85
104
  }
@@ -87,11 +106,12 @@ var require_argument = __commonJS((exports) => {
87
106
  name() {
88
107
  return this._name;
89
108
  }
90
- _concatValue(value, previous) {
109
+ _collectValue(value, previous) {
91
110
  if (previous === this.defaultValue || !Array.isArray(previous)) {
92
111
  return [value];
93
112
  }
94
- return previous.concat(value);
113
+ previous.push(value);
114
+ return previous;
95
115
  }
96
116
  default(value, description) {
97
117
  this.defaultValue = value;
@@ -109,7 +129,7 @@ var require_argument = __commonJS((exports) => {
109
129
  throw new InvalidArgumentError(`Allowed choices are ${this.argChoices.join(", ")}.`);
110
130
  }
111
131
  if (this.variadic) {
112
- return this._concatValue(arg, previous);
132
+ return this._collectValue(arg, previous);
113
133
  }
114
134
  return arg;
115
135
  };
@@ -139,10 +159,14 @@ var require_help = __commonJS((exports) => {
139
159
  class Help {
140
160
  constructor() {
141
161
  this.helpWidth = undefined;
162
+ this.minWidthToWrap = 40;
142
163
  this.sortSubcommands = false;
143
164
  this.sortOptions = false;
144
165
  this.showGlobalOptions = false;
145
166
  }
167
+ prepareContext(contextOptions) {
168
+ this.helpWidth = this.helpWidth ?? contextOptions.helpWidth ?? 80;
169
+ }
146
170
  visibleCommands(cmd) {
147
171
  const visibleCommands = cmd.commands.filter((cmd2) => !cmd2._hidden);
148
172
  const helpCommand = cmd._getHelpCommand();
@@ -217,22 +241,22 @@ var require_help = __commonJS((exports) => {
217
241
  }
218
242
  longestSubcommandTermLength(cmd, helper) {
219
243
  return helper.visibleCommands(cmd).reduce((max, command) => {
220
- return Math.max(max, helper.subcommandTerm(command).length);
244
+ return Math.max(max, this.displayWidth(helper.styleSubcommandTerm(helper.subcommandTerm(command))));
221
245
  }, 0);
222
246
  }
223
247
  longestOptionTermLength(cmd, helper) {
224
248
  return helper.visibleOptions(cmd).reduce((max, option) => {
225
- return Math.max(max, helper.optionTerm(option).length);
249
+ return Math.max(max, this.displayWidth(helper.styleOptionTerm(helper.optionTerm(option))));
226
250
  }, 0);
227
251
  }
228
252
  longestGlobalOptionTermLength(cmd, helper) {
229
253
  return helper.visibleGlobalOptions(cmd).reduce((max, option) => {
230
- return Math.max(max, helper.optionTerm(option).length);
254
+ return Math.max(max, this.displayWidth(helper.styleOptionTerm(helper.optionTerm(option))));
231
255
  }, 0);
232
256
  }
233
257
  longestArgumentTermLength(cmd, helper) {
234
258
  return helper.visibleArguments(cmd).reduce((max, argument) => {
235
- return Math.max(max, helper.argumentTerm(argument).length);
259
+ return Math.max(max, this.displayWidth(helper.styleArgumentTerm(helper.argumentTerm(argument))));
236
260
  }, 0);
237
261
  }
238
262
  commandUsage(cmd) {
@@ -270,7 +294,11 @@ var require_help = __commonJS((exports) => {
270
294
  extraInfo.push(`env: ${option.envVar}`);
271
295
  }
272
296
  if (extraInfo.length > 0) {
273
- return `${option.description} (${extraInfo.join(", ")})`;
297
+ const extraDescription = `(${extraInfo.join(", ")})`;
298
+ if (option.description) {
299
+ return `${option.description} ${extraDescription}`;
300
+ }
301
+ return extraDescription;
274
302
  }
275
303
  return option.description;
276
304
  }
@@ -283,102 +311,202 @@ var require_help = __commonJS((exports) => {
283
311
  extraInfo.push(`default: ${argument.defaultValueDescription || JSON.stringify(argument.defaultValue)}`);
284
312
  }
285
313
  if (extraInfo.length > 0) {
286
- const extraDescripton = `(${extraInfo.join(", ")})`;
314
+ const extraDescription = `(${extraInfo.join(", ")})`;
287
315
  if (argument.description) {
288
- return `${argument.description} ${extraDescripton}`;
316
+ return `${argument.description} ${extraDescription}`;
289
317
  }
290
- return extraDescripton;
318
+ return extraDescription;
291
319
  }
292
320
  return argument.description;
293
321
  }
322
+ formatItemList(heading, items, helper) {
323
+ if (items.length === 0)
324
+ return [];
325
+ return [helper.styleTitle(heading), ...items, ""];
326
+ }
327
+ groupItems(unsortedItems, visibleItems, getGroup) {
328
+ const result = new Map;
329
+ unsortedItems.forEach((item) => {
330
+ const group = getGroup(item);
331
+ if (!result.has(group))
332
+ result.set(group, []);
333
+ });
334
+ visibleItems.forEach((item) => {
335
+ const group = getGroup(item);
336
+ if (!result.has(group)) {
337
+ result.set(group, []);
338
+ }
339
+ result.get(group).push(item);
340
+ });
341
+ return result;
342
+ }
294
343
  formatHelp(cmd, helper) {
295
344
  const termWidth = helper.padWidth(cmd, helper);
296
- const helpWidth = helper.helpWidth || 80;
297
- const itemIndentWidth = 2;
298
- const itemSeparatorWidth = 2;
299
- function formatItem(term, description) {
300
- if (description) {
301
- const fullText = `${term.padEnd(termWidth + itemSeparatorWidth)}${description}`;
302
- return helper.wrap(fullText, helpWidth - itemIndentWidth, termWidth + itemSeparatorWidth);
303
- }
304
- return term;
345
+ const helpWidth = helper.helpWidth ?? 80;
346
+ function callFormatItem(term, description) {
347
+ return helper.formatItem(term, termWidth, description, helper);
305
348
  }
306
- function formatList(textArray) {
307
- return textArray.join(`
308
- `).replace(/^/gm, " ".repeat(itemIndentWidth));
309
- }
310
- let output = [`Usage: ${helper.commandUsage(cmd)}`, ""];
349
+ let output = [
350
+ `${helper.styleTitle("Usage:")} ${helper.styleUsage(helper.commandUsage(cmd))}`,
351
+ ""
352
+ ];
311
353
  const commandDescription = helper.commandDescription(cmd);
312
354
  if (commandDescription.length > 0) {
313
355
  output = output.concat([
314
- helper.wrap(commandDescription, helpWidth, 0),
356
+ helper.boxWrap(helper.styleCommandDescription(commandDescription), helpWidth),
315
357
  ""
316
358
  ]);
317
359
  }
318
360
  const argumentList = helper.visibleArguments(cmd).map((argument) => {
319
- return formatItem(helper.argumentTerm(argument), helper.argumentDescription(argument));
361
+ return callFormatItem(helper.styleArgumentTerm(helper.argumentTerm(argument)), helper.styleArgumentDescription(helper.argumentDescription(argument)));
320
362
  });
321
- if (argumentList.length > 0) {
322
- output = output.concat(["Arguments:", formatList(argumentList), ""]);
323
- }
324
- const optionList = helper.visibleOptions(cmd).map((option) => {
325
- return formatItem(helper.optionTerm(option), helper.optionDescription(option));
363
+ output = output.concat(this.formatItemList("Arguments:", argumentList, helper));
364
+ const optionGroups = this.groupItems(cmd.options, helper.visibleOptions(cmd), (option) => option.helpGroupHeading ?? "Options:");
365
+ optionGroups.forEach((options, group) => {
366
+ const optionList = options.map((option) => {
367
+ return callFormatItem(helper.styleOptionTerm(helper.optionTerm(option)), helper.styleOptionDescription(helper.optionDescription(option)));
368
+ });
369
+ output = output.concat(this.formatItemList(group, optionList, helper));
326
370
  });
327
- if (optionList.length > 0) {
328
- output = output.concat(["Options:", formatList(optionList), ""]);
329
- }
330
- if (this.showGlobalOptions) {
371
+ if (helper.showGlobalOptions) {
331
372
  const globalOptionList = helper.visibleGlobalOptions(cmd).map((option) => {
332
- return formatItem(helper.optionTerm(option), helper.optionDescription(option));
373
+ return callFormatItem(helper.styleOptionTerm(helper.optionTerm(option)), helper.styleOptionDescription(helper.optionDescription(option)));
333
374
  });
334
- if (globalOptionList.length > 0) {
335
- output = output.concat([
336
- "Global Options:",
337
- formatList(globalOptionList),
338
- ""
339
- ]);
340
- }
375
+ output = output.concat(this.formatItemList("Global Options:", globalOptionList, helper));
341
376
  }
342
- const commandList = helper.visibleCommands(cmd).map((cmd2) => {
343
- return formatItem(helper.subcommandTerm(cmd2), helper.subcommandDescription(cmd2));
377
+ const commandGroups = this.groupItems(cmd.commands, helper.visibleCommands(cmd), (sub) => sub.helpGroup() || "Commands:");
378
+ commandGroups.forEach((commands, group) => {
379
+ const commandList = commands.map((sub) => {
380
+ return callFormatItem(helper.styleSubcommandTerm(helper.subcommandTerm(sub)), helper.styleSubcommandDescription(helper.subcommandDescription(sub)));
381
+ });
382
+ output = output.concat(this.formatItemList(group, commandList, helper));
344
383
  });
345
- if (commandList.length > 0) {
346
- output = output.concat(["Commands:", formatList(commandList), ""]);
347
- }
348
384
  return output.join(`
349
385
  `);
350
386
  }
387
+ displayWidth(str) {
388
+ return stripColor(str).length;
389
+ }
390
+ styleTitle(str) {
391
+ return str;
392
+ }
393
+ styleUsage(str) {
394
+ return str.split(" ").map((word) => {
395
+ if (word === "[options]")
396
+ return this.styleOptionText(word);
397
+ if (word === "[command]")
398
+ return this.styleSubcommandText(word);
399
+ if (word[0] === "[" || word[0] === "<")
400
+ return this.styleArgumentText(word);
401
+ return this.styleCommandText(word);
402
+ }).join(" ");
403
+ }
404
+ styleCommandDescription(str) {
405
+ return this.styleDescriptionText(str);
406
+ }
407
+ styleOptionDescription(str) {
408
+ return this.styleDescriptionText(str);
409
+ }
410
+ styleSubcommandDescription(str) {
411
+ return this.styleDescriptionText(str);
412
+ }
413
+ styleArgumentDescription(str) {
414
+ return this.styleDescriptionText(str);
415
+ }
416
+ styleDescriptionText(str) {
417
+ return str;
418
+ }
419
+ styleOptionTerm(str) {
420
+ return this.styleOptionText(str);
421
+ }
422
+ styleSubcommandTerm(str) {
423
+ return str.split(" ").map((word) => {
424
+ if (word === "[options]")
425
+ return this.styleOptionText(word);
426
+ if (word[0] === "[" || word[0] === "<")
427
+ return this.styleArgumentText(word);
428
+ return this.styleSubcommandText(word);
429
+ }).join(" ");
430
+ }
431
+ styleArgumentTerm(str) {
432
+ return this.styleArgumentText(str);
433
+ }
434
+ styleOptionText(str) {
435
+ return str;
436
+ }
437
+ styleArgumentText(str) {
438
+ return str;
439
+ }
440
+ styleSubcommandText(str) {
441
+ return str;
442
+ }
443
+ styleCommandText(str) {
444
+ return str;
445
+ }
351
446
  padWidth(cmd, helper) {
352
447
  return Math.max(helper.longestOptionTermLength(cmd, helper), helper.longestGlobalOptionTermLength(cmd, helper), helper.longestSubcommandTermLength(cmd, helper), helper.longestArgumentTermLength(cmd, helper));
353
448
  }
354
- wrap(str, width, indent, minColumnWidth = 40) {
355
- const indents = " \\f\\t\\v   -    \uFEFF";
356
- const manualIndent = new RegExp(`[\\n][${indents}]+`);
357
- if (str.match(manualIndent))
358
- return str;
359
- const columnWidth = width - indent;
360
- if (columnWidth < minColumnWidth)
449
+ preformatted(str) {
450
+ return /\n[^\S\r\n]/.test(str);
451
+ }
452
+ formatItem(term, termWidth, description, helper) {
453
+ const itemIndent = 2;
454
+ const itemIndentStr = " ".repeat(itemIndent);
455
+ if (!description)
456
+ return itemIndentStr + term;
457
+ const paddedTerm = term.padEnd(termWidth + term.length - helper.displayWidth(term));
458
+ const spacerWidth = 2;
459
+ const helpWidth = this.helpWidth ?? 80;
460
+ const remainingWidth = helpWidth - termWidth - spacerWidth - itemIndent;
461
+ let formattedDescription;
462
+ if (remainingWidth < this.minWidthToWrap || helper.preformatted(description)) {
463
+ formattedDescription = description;
464
+ } else {
465
+ const wrappedDescription = helper.boxWrap(description, remainingWidth);
466
+ formattedDescription = wrappedDescription.replace(/\n/g, `
467
+ ` + " ".repeat(termWidth + spacerWidth));
468
+ }
469
+ return itemIndentStr + paddedTerm + " ".repeat(spacerWidth) + formattedDescription.replace(/\n/g, `
470
+ ${itemIndentStr}`);
471
+ }
472
+ boxWrap(str, width) {
473
+ if (width < this.minWidthToWrap)
361
474
  return str;
362
- const leadingStr = str.slice(0, indent);
363
- const columnText = str.slice(indent).replace(`\r
364
- `, `
365
- `);
366
- const indentString = " ".repeat(indent);
367
- const zeroWidthSpace = "​";
368
- const breaks = `\\s${zeroWidthSpace}`;
369
- const regex = new RegExp(`
370
- |.{1,${columnWidth - 1}}([${breaks}]|$)|[^${breaks}]+?([${breaks}]|$)`, "g");
371
- const lines = columnText.match(regex) || [];
372
- return leadingStr + lines.map((line, i) => {
373
- if (line === `
374
- `)
375
- return "";
376
- return (i > 0 ? indentString : "") + line.trimEnd();
377
- }).join(`
475
+ const rawLines = str.split(/\r\n|\n/);
476
+ const chunkPattern = /[\s]*[^\s]+/g;
477
+ const wrappedLines = [];
478
+ rawLines.forEach((line) => {
479
+ const chunks = line.match(chunkPattern);
480
+ if (chunks === null) {
481
+ wrappedLines.push("");
482
+ return;
483
+ }
484
+ let sumChunks = [chunks.shift()];
485
+ let sumWidth = this.displayWidth(sumChunks[0]);
486
+ chunks.forEach((chunk) => {
487
+ const visibleWidth = this.displayWidth(chunk);
488
+ if (sumWidth + visibleWidth <= width) {
489
+ sumChunks.push(chunk);
490
+ sumWidth += visibleWidth;
491
+ return;
492
+ }
493
+ wrappedLines.push(sumChunks.join(""));
494
+ const nextChunk = chunk.trimStart();
495
+ sumChunks = [nextChunk];
496
+ sumWidth = this.displayWidth(nextChunk);
497
+ });
498
+ wrappedLines.push(sumChunks.join(""));
499
+ });
500
+ return wrappedLines.join(`
378
501
  `);
379
502
  }
380
503
  }
504
+ function stripColor(str) {
505
+ const sgrPattern = /\x1b\[\d*(;\d*)*m/g;
506
+ return str.replace(sgrPattern, "");
507
+ }
381
508
  exports.Help = Help;
509
+ exports.stripColor = stripColor;
382
510
  });
383
511
 
384
512
  // node_modules/commander/lib/option.js
@@ -409,6 +537,7 @@ var require_option = __commonJS((exports) => {
409
537
  this.argChoices = undefined;
410
538
  this.conflictsWith = [];
411
539
  this.implied = undefined;
540
+ this.helpGroupHeading = undefined;
412
541
  }
413
542
  default(value, description) {
414
543
  this.defaultValue = value;
@@ -447,11 +576,12 @@ var require_option = __commonJS((exports) => {
447
576
  this.hidden = !!hide;
448
577
  return this;
449
578
  }
450
- _concatValue(value, previous) {
579
+ _collectValue(value, previous) {
451
580
  if (previous === this.defaultValue || !Array.isArray(previous)) {
452
581
  return [value];
453
582
  }
454
- return previous.concat(value);
583
+ previous.push(value);
584
+ return previous;
455
585
  }
456
586
  choices(values) {
457
587
  this.argChoices = values.slice();
@@ -460,7 +590,7 @@ var require_option = __commonJS((exports) => {
460
590
  throw new InvalidArgumentError(`Allowed choices are ${this.argChoices.join(", ")}.`);
461
591
  }
462
592
  if (this.variadic) {
463
- return this._concatValue(arg, previous);
593
+ return this._collectValue(arg, previous);
464
594
  }
465
595
  return arg;
466
596
  };
@@ -473,7 +603,14 @@ var require_option = __commonJS((exports) => {
473
603
  return this.short.replace(/^-/, "");
474
604
  }
475
605
  attributeName() {
476
- return camelcase(this.name().replace(/^no-/, ""));
606
+ if (this.negate) {
607
+ return camelcase(this.name().replace(/^no-/, ""));
608
+ }
609
+ return camelcase(this.name());
610
+ }
611
+ helpGroup(heading) {
612
+ this.helpGroupHeading = heading;
613
+ return this;
477
614
  }
478
615
  is(arg) {
479
616
  return this.short === arg || this.long === arg;
@@ -518,14 +655,38 @@ var require_option = __commonJS((exports) => {
518
655
  function splitOptionFlags(flags) {
519
656
  let shortFlag;
520
657
  let longFlag;
521
- const flagParts = flags.split(/[ |,]+/);
522
- if (flagParts.length > 1 && !/^[[<]/.test(flagParts[1]))
658
+ const shortFlagExp = /^-[^-]$/;
659
+ const longFlagExp = /^--[^-]/;
660
+ const flagParts = flags.split(/[ |,]+/).concat("guard");
661
+ if (shortFlagExp.test(flagParts[0]))
662
+ shortFlag = flagParts.shift();
663
+ if (longFlagExp.test(flagParts[0]))
664
+ longFlag = flagParts.shift();
665
+ if (!shortFlag && shortFlagExp.test(flagParts[0]))
523
666
  shortFlag = flagParts.shift();
524
- longFlag = flagParts.shift();
525
- if (!shortFlag && /^-[^-]$/.test(longFlag)) {
667
+ if (!shortFlag && longFlagExp.test(flagParts[0])) {
526
668
  shortFlag = longFlag;
527
- longFlag = undefined;
528
- }
669
+ longFlag = flagParts.shift();
670
+ }
671
+ if (flagParts[0].startsWith("-")) {
672
+ const unsupportedFlag = flagParts[0];
673
+ const baseError = `option creation failed due to '${unsupportedFlag}' in option flags '${flags}'`;
674
+ if (/^-[^-][^-]/.test(unsupportedFlag))
675
+ throw new Error(`${baseError}
676
+ - a short flag is a single dash and a single character
677
+ - either use a single dash and a single character (for a short flag)
678
+ - or use a double dash for a long option (and can have two, like '--ws, --workspace')`);
679
+ if (shortFlagExp.test(unsupportedFlag))
680
+ throw new Error(`${baseError}
681
+ - too many short flags`);
682
+ if (longFlagExp.test(unsupportedFlag))
683
+ throw new Error(`${baseError}
684
+ - too many long flags`);
685
+ throw new Error(`${baseError}
686
+ - unrecognised flag format`);
687
+ }
688
+ if (shortFlag === undefined && longFlag === undefined)
689
+ throw new Error(`option creation failed due to no flags found in '${flags}'.`);
529
690
  return { shortFlag, longFlag };
530
691
  }
531
692
  exports.Option = Option;
@@ -614,7 +775,7 @@ var require_command = __commonJS((exports) => {
614
775
  var process2 = __require("node:process");
615
776
  var { Argument, humanReadableArgName } = require_argument();
616
777
  var { CommanderError } = require_error();
617
- var { Help } = require_help();
778
+ var { Help, stripColor } = require_help();
618
779
  var { Option, DualOptions } = require_option();
619
780
  var { suggestSimilar } = require_suggestSimilar();
620
781
 
@@ -625,7 +786,7 @@ var require_command = __commonJS((exports) => {
625
786
  this.options = [];
626
787
  this.parent = null;
627
788
  this._allowUnknownOption = false;
628
- this._allowExcessArguments = true;
789
+ this._allowExcessArguments = false;
629
790
  this.registeredArguments = [];
630
791
  this._args = this.registeredArguments;
631
792
  this.args = [];
@@ -652,18 +813,25 @@ var require_command = __commonJS((exports) => {
652
813
  this._lifeCycleHooks = {};
653
814
  this._showHelpAfterError = false;
654
815
  this._showSuggestionAfterError = true;
816
+ this._savedState = null;
655
817
  this._outputConfiguration = {
656
818
  writeOut: (str) => process2.stdout.write(str),
657
819
  writeErr: (str) => process2.stderr.write(str),
820
+ outputError: (str, write) => write(str),
658
821
  getOutHelpWidth: () => process2.stdout.isTTY ? process2.stdout.columns : undefined,
659
822
  getErrHelpWidth: () => process2.stderr.isTTY ? process2.stderr.columns : undefined,
660
- outputError: (str, write) => write(str)
823
+ getOutHasColors: () => useColor() ?? (process2.stdout.isTTY && process2.stdout.hasColors?.()),
824
+ getErrHasColors: () => useColor() ?? (process2.stderr.isTTY && process2.stderr.hasColors?.()),
825
+ stripColor: (str) => stripColor(str)
661
826
  };
662
827
  this._hidden = false;
663
828
  this._helpOption = undefined;
664
829
  this._addImplicitHelpCommand = undefined;
665
830
  this._helpCommand = undefined;
666
831
  this._helpConfiguration = {};
832
+ this._helpGroupHeading = undefined;
833
+ this._defaultCommandGroup = undefined;
834
+ this._defaultOptionGroup = undefined;
667
835
  }
668
836
  copyInheritedSettings(sourceCommand) {
669
837
  this._outputConfiguration = sourceCommand._outputConfiguration;
@@ -728,7 +896,10 @@ var require_command = __commonJS((exports) => {
728
896
  configureOutput(configuration) {
729
897
  if (configuration === undefined)
730
898
  return this._outputConfiguration;
731
- Object.assign(this._outputConfiguration, configuration);
899
+ this._outputConfiguration = {
900
+ ...this._outputConfiguration,
901
+ ...configuration
902
+ };
732
903
  return this;
733
904
  }
734
905
  showHelpAfterError(displayHelp = true) {
@@ -759,12 +930,12 @@ var require_command = __commonJS((exports) => {
759
930
  createArgument(name, description) {
760
931
  return new Argument(name, description);
761
932
  }
762
- argument(name, description, fn, defaultValue) {
933
+ argument(name, description, parseArg, defaultValue) {
763
934
  const argument = this.createArgument(name, description);
764
- if (typeof fn === "function") {
765
- argument.default(defaultValue).argParser(fn);
935
+ if (typeof parseArg === "function") {
936
+ argument.default(defaultValue).argParser(parseArg);
766
937
  } else {
767
- argument.default(fn);
938
+ argument.default(parseArg);
768
939
  }
769
940
  this.addArgument(argument);
770
941
  return this;
@@ -777,7 +948,7 @@ var require_command = __commonJS((exports) => {
777
948
  }
778
949
  addArgument(argument) {
779
950
  const previousArgument = this.registeredArguments.slice(-1)[0];
780
- if (previousArgument && previousArgument.variadic) {
951
+ if (previousArgument?.variadic) {
781
952
  throw new Error(`only the last argument can be variadic '${previousArgument.name()}'`);
782
953
  }
783
954
  if (argument.required && argument.defaultValue !== undefined && argument.parseArg === undefined) {
@@ -789,10 +960,13 @@ var require_command = __commonJS((exports) => {
789
960
  helpCommand(enableOrNameAndArgs, description) {
790
961
  if (typeof enableOrNameAndArgs === "boolean") {
791
962
  this._addImplicitHelpCommand = enableOrNameAndArgs;
963
+ if (enableOrNameAndArgs && this._defaultCommandGroup) {
964
+ this._initCommandGroup(this._getHelpCommand());
965
+ }
792
966
  return this;
793
967
  }
794
- enableOrNameAndArgs = enableOrNameAndArgs ?? "help [command]";
795
- const [, helpName, helpArgs] = enableOrNameAndArgs.match(/([^ ]+) *(.*)/);
968
+ const nameAndArgs = enableOrNameAndArgs ?? "help [command]";
969
+ const [, helpName, helpArgs] = nameAndArgs.match(/([^ ]+) *(.*)/);
796
970
  const helpDescription = description ?? "display help for command";
797
971
  const helpCommand = this.createCommand(helpName);
798
972
  helpCommand.helpOption(false);
@@ -802,6 +976,8 @@ var require_command = __commonJS((exports) => {
802
976
  helpCommand.description(helpDescription);
803
977
  this._addImplicitHelpCommand = true;
804
978
  this._helpCommand = helpCommand;
979
+ if (enableOrNameAndArgs || description)
980
+ this._initCommandGroup(helpCommand);
805
981
  return this;
806
982
  }
807
983
  addHelpCommand(helpCommand, deprecatedDescription) {
@@ -811,6 +987,7 @@ var require_command = __commonJS((exports) => {
811
987
  }
812
988
  this._addImplicitHelpCommand = true;
813
989
  this._helpCommand = helpCommand;
990
+ this._initCommandGroup(helpCommand);
814
991
  return this;
815
992
  }
816
993
  _getHelpCommand() {
@@ -890,6 +1067,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
890
1067
  throw new Error(`Cannot add option '${option.flags}'${this._name && ` to command '${this._name}'`} due to conflicting flag '${matchingFlag}'
891
1068
  - already used by option '${matchingOption.flags}'`);
892
1069
  }
1070
+ this._initOptionGroup(option);
893
1071
  this.options.push(option);
894
1072
  }
895
1073
  _registerCommand(command) {
@@ -902,6 +1080,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
902
1080
  const newCmd = knownBy(command).join("|");
903
1081
  throw new Error(`cannot add command '${newCmd}' as already have command '${existingCmd}'`);
904
1082
  }
1083
+ this._initCommandGroup(command);
905
1084
  this.commands.push(command);
906
1085
  }
907
1086
  addOption(option) {
@@ -924,7 +1103,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
924
1103
  if (val !== null && option.parseArg) {
925
1104
  val = this._callParseArg(option, val, oldValue, invalidValueMessage);
926
1105
  } else if (val !== null && option.variadic) {
927
- val = option._concatValue(val, oldValue);
1106
+ val = option._collectValue(val, oldValue);
928
1107
  }
929
1108
  if (val == null) {
930
1109
  if (option.negate) {
@@ -1089,15 +1268,53 @@ Expecting one of '${allowedValues.join("', '")}'`);
1089
1268
  return userArgs;
1090
1269
  }
1091
1270
  parse(argv, parseOptions) {
1271
+ this._prepareForParse();
1092
1272
  const userArgs = this._prepareUserArgs(argv, parseOptions);
1093
1273
  this._parseCommand([], userArgs);
1094
1274
  return this;
1095
1275
  }
1096
1276
  async parseAsync(argv, parseOptions) {
1277
+ this._prepareForParse();
1097
1278
  const userArgs = this._prepareUserArgs(argv, parseOptions);
1098
1279
  await this._parseCommand([], userArgs);
1099
1280
  return this;
1100
1281
  }
1282
+ _prepareForParse() {
1283
+ if (this._savedState === null) {
1284
+ this.saveStateBeforeParse();
1285
+ } else {
1286
+ this.restoreStateBeforeParse();
1287
+ }
1288
+ }
1289
+ saveStateBeforeParse() {
1290
+ this._savedState = {
1291
+ _name: this._name,
1292
+ _optionValues: { ...this._optionValues },
1293
+ _optionValueSources: { ...this._optionValueSources }
1294
+ };
1295
+ }
1296
+ restoreStateBeforeParse() {
1297
+ if (this._storeOptionsAsProperties)
1298
+ throw new Error(`Can not call parse again when storeOptionsAsProperties is true.
1299
+ - either make a new Command for each call to parse, or stop storing options as properties`);
1300
+ this._name = this._savedState._name;
1301
+ this._scriptPath = null;
1302
+ this.rawArgs = [];
1303
+ this._optionValues = { ...this._savedState._optionValues };
1304
+ this._optionValueSources = { ...this._savedState._optionValueSources };
1305
+ this.args = [];
1306
+ this.processedArgs = [];
1307
+ }
1308
+ _checkForMissingExecutable(executableFile, executableDir, subcommandName) {
1309
+ if (fs.existsSync(executableFile))
1310
+ return;
1311
+ const executableDirMessage = executableDir ? `searched for local subcommand relative to directory '${executableDir}'` : "no directory for search for local subcommand, use .executableDir() to supply a custom directory";
1312
+ const executableMissing = `'${executableFile}' does not exist
1313
+ - if '${subcommandName}' is not meant to be an executable command, remove description parameter from '.command()' and use '.description()' instead
1314
+ - if the default executable name is not suitable, use the executableFile option to supply a custom name or path
1315
+ - ${executableDirMessage}`;
1316
+ throw new Error(executableMissing);
1317
+ }
1101
1318
  _executeSubCommand(subcommand, args) {
1102
1319
  args = args.slice();
1103
1320
  let launchWithNode = false;
@@ -1121,7 +1338,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1121
1338
  let resolvedScriptPath;
1122
1339
  try {
1123
1340
  resolvedScriptPath = fs.realpathSync(this._scriptPath);
1124
- } catch (err) {
1341
+ } catch {
1125
1342
  resolvedScriptPath = this._scriptPath;
1126
1343
  }
1127
1344
  executableDir = path.resolve(path.dirname(resolvedScriptPath), executableDir);
@@ -1147,6 +1364,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1147
1364
  proc = childProcess.spawn(executableFile, args, { stdio: "inherit" });
1148
1365
  }
1149
1366
  } else {
1367
+ this._checkForMissingExecutable(executableFile, executableDir, subcommand._name);
1150
1368
  args.unshift(executableFile);
1151
1369
  args = incrementNodeInspectorPort(process2.execArgv).concat(args);
1152
1370
  proc = childProcess.spawn(process2.execPath, args, { stdio: "inherit" });
@@ -1172,12 +1390,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1172
1390
  });
1173
1391
  proc.on("error", (err) => {
1174
1392
  if (err.code === "ENOENT") {
1175
- const executableDirMessage = executableDir ? `searched for local subcommand relative to directory '${executableDir}'` : "no directory for search for local subcommand, use .executableDir() to supply a custom directory";
1176
- const executableMissing = `'${executableFile}' does not exist
1177
- - if '${subcommand._name}' is not meant to be an executable command, remove description parameter from '.command()' and use '.description()' instead
1178
- - if the default executable name is not suitable, use the executableFile option to supply a custom name or path
1179
- - ${executableDirMessage}`;
1180
- throw new Error(executableMissing);
1393
+ this._checkForMissingExecutable(executableFile, executableDir, subcommand._name);
1181
1394
  } else if (err.code === "EACCES") {
1182
1395
  throw new Error(`'${executableFile}' not executable`);
1183
1396
  }
@@ -1195,6 +1408,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1195
1408
  const subCommand = this._findCommand(commandName);
1196
1409
  if (!subCommand)
1197
1410
  this.help({ error: true });
1411
+ subCommand._prepareForParse();
1198
1412
  let promiseChain;
1199
1413
  promiseChain = this._chainOrCallSubCommandHook(promiseChain, subCommand, "preSubcommand");
1200
1414
  promiseChain = this._chainOrCall(promiseChain, () => {
@@ -1264,7 +1478,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1264
1478
  this.processedArgs = processedArgs;
1265
1479
  }
1266
1480
  _chainOrCall(promise, fn) {
1267
- if (promise && promise.then && typeof promise.then === "function") {
1481
+ if (promise?.then && typeof promise.then === "function") {
1268
1482
  return promise.then(() => fn());
1269
1483
  }
1270
1484
  return fn();
@@ -1341,7 +1555,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1341
1555
  promiseChain = this._chainOrCallHooks(promiseChain, "postAction");
1342
1556
  return promiseChain;
1343
1557
  }
1344
- if (this.parent && this.parent.listenerCount(commandEvent)) {
1558
+ if (this.parent?.listenerCount(commandEvent)) {
1345
1559
  checkForUnknownOptions();
1346
1560
  this._processArguments();
1347
1561
  this.parent.emit(commandEvent, operands, unknown);
@@ -1403,24 +1617,31 @@ Expecting one of '${allowedValues.join("', '")}'`);
1403
1617
  cmd._checkForConflictingLocalOptions();
1404
1618
  });
1405
1619
  }
1406
- parseOptions(argv) {
1620
+ parseOptions(args) {
1407
1621
  const operands = [];
1408
1622
  const unknown = [];
1409
1623
  let dest = operands;
1410
- const args = argv.slice();
1411
1624
  function maybeOption(arg) {
1412
1625
  return arg.length > 1 && arg[0] === "-";
1413
1626
  }
1627
+ const negativeNumberArg = (arg) => {
1628
+ if (!/^-(\d+|\d*\.\d+)(e[+-]?\d+)?$/.test(arg))
1629
+ return false;
1630
+ return !this._getCommandAndAncestors().some((cmd) => cmd.options.map((opt) => opt.short).some((short) => /^-\d$/.test(short)));
1631
+ };
1414
1632
  let activeVariadicOption = null;
1415
- while (args.length) {
1416
- const arg = args.shift();
1633
+ let activeGroup = null;
1634
+ let i = 0;
1635
+ while (i < args.length || activeGroup) {
1636
+ const arg = activeGroup ?? args[i++];
1637
+ activeGroup = null;
1417
1638
  if (arg === "--") {
1418
1639
  if (dest === unknown)
1419
1640
  dest.push(arg);
1420
- dest.push(...args);
1641
+ dest.push(...args.slice(i));
1421
1642
  break;
1422
1643
  }
1423
- if (activeVariadicOption && !maybeOption(arg)) {
1644
+ if (activeVariadicOption && (!maybeOption(arg) || negativeNumberArg(arg))) {
1424
1645
  this.emit(`option:${activeVariadicOption.name()}`, arg);
1425
1646
  continue;
1426
1647
  }
@@ -1429,14 +1650,14 @@ Expecting one of '${allowedValues.join("', '")}'`);
1429
1650
  const option = this._findOption(arg);
1430
1651
  if (option) {
1431
1652
  if (option.required) {
1432
- const value = args.shift();
1653
+ const value = args[i++];
1433
1654
  if (value === undefined)
1434
1655
  this.optionMissingArgument(option);
1435
1656
  this.emit(`option:${option.name()}`, value);
1436
1657
  } else if (option.optional) {
1437
1658
  let value = null;
1438
- if (args.length > 0 && !maybeOption(args[0])) {
1439
- value = args.shift();
1659
+ if (i < args.length && (!maybeOption(args[i]) || negativeNumberArg(args[i]))) {
1660
+ value = args[i++];
1440
1661
  }
1441
1662
  this.emit(`option:${option.name()}`, value);
1442
1663
  } else {
@@ -1453,7 +1674,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1453
1674
  this.emit(`option:${option.name()}`, arg.slice(2));
1454
1675
  } else {
1455
1676
  this.emit(`option:${option.name()}`);
1456
- args.unshift(`-${arg.slice(2)}`);
1677
+ activeGroup = `-${arg.slice(2)}`;
1457
1678
  }
1458
1679
  continue;
1459
1680
  }
@@ -1466,31 +1687,24 @@ Expecting one of '${allowedValues.join("', '")}'`);
1466
1687
  continue;
1467
1688
  }
1468
1689
  }
1469
- if (maybeOption(arg)) {
1690
+ if (dest === operands && maybeOption(arg) && !(this.commands.length === 0 && negativeNumberArg(arg))) {
1470
1691
  dest = unknown;
1471
1692
  }
1472
1693
  if ((this._enablePositionalOptions || this._passThroughOptions) && operands.length === 0 && unknown.length === 0) {
1473
1694
  if (this._findCommand(arg)) {
1474
1695
  operands.push(arg);
1475
- if (args.length > 0)
1476
- unknown.push(...args);
1696
+ unknown.push(...args.slice(i));
1477
1697
  break;
1478
1698
  } else if (this._getHelpCommand() && arg === this._getHelpCommand().name()) {
1479
- operands.push(arg);
1480
- if (args.length > 0)
1481
- operands.push(...args);
1699
+ operands.push(arg, ...args.slice(i));
1482
1700
  break;
1483
1701
  } else if (this._defaultCommandName) {
1484
- unknown.push(arg);
1485
- if (args.length > 0)
1486
- unknown.push(...args);
1702
+ unknown.push(arg, ...args.slice(i));
1487
1703
  break;
1488
1704
  }
1489
1705
  }
1490
1706
  if (this._passThroughOptions) {
1491
- dest.push(arg);
1492
- if (args.length > 0)
1493
- dest.push(...args);
1707
+ dest.push(arg, ...args.slice(i));
1494
1708
  break;
1495
1709
  }
1496
1710
  dest.push(arg);
@@ -1701,6 +1915,32 @@ Expecting one of '${allowedValues.join("', '")}'`);
1701
1915
  this._name = str;
1702
1916
  return this;
1703
1917
  }
1918
+ helpGroup(heading) {
1919
+ if (heading === undefined)
1920
+ return this._helpGroupHeading ?? "";
1921
+ this._helpGroupHeading = heading;
1922
+ return this;
1923
+ }
1924
+ commandsGroup(heading) {
1925
+ if (heading === undefined)
1926
+ return this._defaultCommandGroup ?? "";
1927
+ this._defaultCommandGroup = heading;
1928
+ return this;
1929
+ }
1930
+ optionsGroup(heading) {
1931
+ if (heading === undefined)
1932
+ return this._defaultOptionGroup ?? "";
1933
+ this._defaultOptionGroup = heading;
1934
+ return this;
1935
+ }
1936
+ _initOptionGroup(option) {
1937
+ if (this._defaultOptionGroup && !option.helpGroupHeading)
1938
+ option.helpGroup(this._defaultOptionGroup);
1939
+ }
1940
+ _initCommandGroup(cmd) {
1941
+ if (this._defaultCommandGroup && !cmd.helpGroup())
1942
+ cmd.helpGroup(this._defaultCommandGroup);
1943
+ }
1704
1944
  nameFromFilename(filename) {
1705
1945
  this._name = path.basename(filename, path.extname(filename));
1706
1946
  return this;
@@ -1713,23 +1953,38 @@ Expecting one of '${allowedValues.join("', '")}'`);
1713
1953
  }
1714
1954
  helpInformation(contextOptions) {
1715
1955
  const helper = this.createHelp();
1716
- if (helper.helpWidth === undefined) {
1717
- helper.helpWidth = contextOptions && contextOptions.error ? this._outputConfiguration.getErrHelpWidth() : this._outputConfiguration.getOutHelpWidth();
1718
- }
1719
- return helper.formatHelp(this, helper);
1956
+ const context = this._getOutputContext(contextOptions);
1957
+ helper.prepareContext({
1958
+ error: context.error,
1959
+ helpWidth: context.helpWidth,
1960
+ outputHasColors: context.hasColors
1961
+ });
1962
+ const text = helper.formatHelp(this, helper);
1963
+ if (context.hasColors)
1964
+ return text;
1965
+ return this._outputConfiguration.stripColor(text);
1720
1966
  }
1721
- _getHelpContext(contextOptions) {
1967
+ _getOutputContext(contextOptions) {
1722
1968
  contextOptions = contextOptions || {};
1723
- const context = { error: !!contextOptions.error };
1724
- let write;
1725
- if (context.error) {
1726
- write = (arg) => this._outputConfiguration.writeErr(arg);
1969
+ const error = !!contextOptions.error;
1970
+ let baseWrite;
1971
+ let hasColors;
1972
+ let helpWidth;
1973
+ if (error) {
1974
+ baseWrite = (str) => this._outputConfiguration.writeErr(str);
1975
+ hasColors = this._outputConfiguration.getErrHasColors();
1976
+ helpWidth = this._outputConfiguration.getErrHelpWidth();
1727
1977
  } else {
1728
- write = (arg) => this._outputConfiguration.writeOut(arg);
1729
- }
1730
- context.write = contextOptions.write || write;
1731
- context.command = this;
1732
- return context;
1978
+ baseWrite = (str) => this._outputConfiguration.writeOut(str);
1979
+ hasColors = this._outputConfiguration.getOutHasColors();
1980
+ helpWidth = this._outputConfiguration.getOutHelpWidth();
1981
+ }
1982
+ const write = (str) => {
1983
+ if (!hasColors)
1984
+ str = this._outputConfiguration.stripColor(str);
1985
+ return baseWrite(str);
1986
+ };
1987
+ return { error, write, hasColors, helpWidth };
1733
1988
  }
1734
1989
  outputHelp(contextOptions) {
1735
1990
  let deprecatedCallback;
@@ -1737,35 +1992,44 @@ Expecting one of '${allowedValues.join("', '")}'`);
1737
1992
  deprecatedCallback = contextOptions;
1738
1993
  contextOptions = undefined;
1739
1994
  }
1740
- const context = this._getHelpContext(contextOptions);
1741
- this._getCommandAndAncestors().reverse().forEach((command) => command.emit("beforeAllHelp", context));
1742
- this.emit("beforeHelp", context);
1743
- let helpInformation = this.helpInformation(context);
1995
+ const outputContext = this._getOutputContext(contextOptions);
1996
+ const eventContext = {
1997
+ error: outputContext.error,
1998
+ write: outputContext.write,
1999
+ command: this
2000
+ };
2001
+ this._getCommandAndAncestors().reverse().forEach((command) => command.emit("beforeAllHelp", eventContext));
2002
+ this.emit("beforeHelp", eventContext);
2003
+ let helpInformation = this.helpInformation({ error: outputContext.error });
1744
2004
  if (deprecatedCallback) {
1745
2005
  helpInformation = deprecatedCallback(helpInformation);
1746
2006
  if (typeof helpInformation !== "string" && !Buffer.isBuffer(helpInformation)) {
1747
2007
  throw new Error("outputHelp callback must return a string or a Buffer");
1748
2008
  }
1749
2009
  }
1750
- context.write(helpInformation);
2010
+ outputContext.write(helpInformation);
1751
2011
  if (this._getHelpOption()?.long) {
1752
2012
  this.emit(this._getHelpOption().long);
1753
2013
  }
1754
- this.emit("afterHelp", context);
1755
- this._getCommandAndAncestors().forEach((command) => command.emit("afterAllHelp", context));
2014
+ this.emit("afterHelp", eventContext);
2015
+ this._getCommandAndAncestors().forEach((command) => command.emit("afterAllHelp", eventContext));
1756
2016
  }
1757
2017
  helpOption(flags, description) {
1758
2018
  if (typeof flags === "boolean") {
1759
2019
  if (flags) {
1760
- this._helpOption = this._helpOption ?? undefined;
2020
+ if (this._helpOption === null)
2021
+ this._helpOption = undefined;
2022
+ if (this._defaultOptionGroup) {
2023
+ this._initOptionGroup(this._getHelpOption());
2024
+ }
1761
2025
  } else {
1762
2026
  this._helpOption = null;
1763
2027
  }
1764
2028
  return this;
1765
2029
  }
1766
- flags = flags ?? "-h, --help";
1767
- description = description ?? "display help for command";
1768
- this._helpOption = this.createOption(flags, description);
2030
+ this._helpOption = this.createOption(flags ?? "-h, --help", description ?? "display help for command");
2031
+ if (flags || description)
2032
+ this._initOptionGroup(this._helpOption);
1769
2033
  return this;
1770
2034
  }
1771
2035
  _getHelpOption() {
@@ -1776,11 +2040,12 @@ Expecting one of '${allowedValues.join("', '")}'`);
1776
2040
  }
1777
2041
  addHelpOption(option) {
1778
2042
  this._helpOption = option;
2043
+ this._initOptionGroup(option);
1779
2044
  return this;
1780
2045
  }
1781
2046
  help(contextOptions) {
1782
2047
  this.outputHelp(contextOptions);
1783
- let exitCode = process2.exitCode || 0;
2048
+ let exitCode = Number(process2.exitCode ?? 0);
1784
2049
  if (exitCode === 0 && contextOptions && typeof contextOptions !== "function" && contextOptions.error) {
1785
2050
  exitCode = 1;
1786
2051
  }
@@ -1845,7 +2110,15 @@ Expecting one of '${allowedValues.join("', '")}'`);
1845
2110
  return arg;
1846
2111
  });
1847
2112
  }
2113
+ function useColor() {
2114
+ if (process2.env.NO_COLOR || process2.env.FORCE_COLOR === "0" || process2.env.FORCE_COLOR === "false")
2115
+ return false;
2116
+ if (process2.env.FORCE_COLOR || process2.env.CLICOLOR_FORCE !== undefined)
2117
+ return true;
2118
+ return;
2119
+ }
1848
2120
  exports.Command = Command;
2121
+ exports.useColor = useColor;
1849
2122
  });
1850
2123
 
1851
2124
  // node_modules/commander/index.js
@@ -13046,6 +13319,92 @@ var require_dist2 = __commonJS((exports, module) => {
13046
13319
  exports.default = formatsPlugin;
13047
13320
  });
13048
13321
 
13322
+ // lib/util.ts
13323
+ var exports_util2 = {};
13324
+ __export(exports_util2, {
13325
+ resolveBaseUrls: () => resolveBaseUrls2,
13326
+ normalizeBaseUrl: () => normalizeBaseUrl2,
13327
+ maskSecret: () => maskSecret2,
13328
+ formatHttpError: () => formatHttpError2
13329
+ });
13330
+ function isHtmlContent2(text) {
13331
+ const trimmed = text.trim();
13332
+ return trimmed.startsWith("<!DOCTYPE") || trimmed.startsWith("<html") || trimmed.startsWith("<HTML");
13333
+ }
13334
+ function formatHttpError2(operation, status, responseBody) {
13335
+ const statusMessage = HTTP_STATUS_MESSAGES2[status] || "Request failed";
13336
+ let errMsg = `${operation}: HTTP ${status} - ${statusMessage}`;
13337
+ if (responseBody) {
13338
+ if (isHtmlContent2(responseBody)) {
13339
+ return errMsg;
13340
+ }
13341
+ try {
13342
+ const errObj = JSON.parse(responseBody);
13343
+ const message = errObj.message || errObj.error || errObj.detail;
13344
+ if (message && typeof message === "string") {
13345
+ errMsg += `
13346
+ ${message}`;
13347
+ } else {
13348
+ errMsg += `
13349
+ ${JSON.stringify(errObj, null, 2)}`;
13350
+ }
13351
+ } catch {
13352
+ const trimmed = responseBody.trim();
13353
+ if (trimmed.length > 0 && trimmed.length < 500) {
13354
+ errMsg += `
13355
+ ${trimmed}`;
13356
+ }
13357
+ }
13358
+ }
13359
+ return errMsg;
13360
+ }
13361
+ function maskSecret2(secret) {
13362
+ if (!secret)
13363
+ return "";
13364
+ if (secret.length <= 8)
13365
+ return "****";
13366
+ if (secret.length <= 16)
13367
+ return `${secret.slice(0, 4)}${"*".repeat(secret.length - 8)}${secret.slice(-4)}`;
13368
+ return `${secret.slice(0, Math.min(12, secret.length - 8))}${"*".repeat(Math.max(4, secret.length - 16))}${secret.slice(-4)}`;
13369
+ }
13370
+ function normalizeBaseUrl2(value) {
13371
+ const trimmed = (value || "").replace(/\/$/, "");
13372
+ try {
13373
+ new URL(trimmed);
13374
+ } catch {
13375
+ throw new Error(`Invalid base URL: ${value}`);
13376
+ }
13377
+ return trimmed;
13378
+ }
13379
+ function resolveBaseUrls2(opts, cfg, defaults2 = {}) {
13380
+ const defApi = defaults2.apiBaseUrl || "https://postgres.ai/api/general/";
13381
+ const defUi = defaults2.uiBaseUrl || "https://console.postgres.ai";
13382
+ const defStorage = defaults2.storageBaseUrl || "https://postgres.ai/storage";
13383
+ const apiCandidate = opts?.apiBaseUrl || process.env.PGAI_API_BASE_URL || cfg?.baseUrl || defApi;
13384
+ const uiCandidate = opts?.uiBaseUrl || process.env.PGAI_UI_BASE_URL || defUi;
13385
+ const storageCandidate = opts?.storageBaseUrl || process.env.PGAI_STORAGE_BASE_URL || cfg?.storageBaseUrl || defStorage;
13386
+ return {
13387
+ apiBaseUrl: normalizeBaseUrl2(apiCandidate),
13388
+ uiBaseUrl: normalizeBaseUrl2(uiCandidate),
13389
+ storageBaseUrl: normalizeBaseUrl2(storageCandidate)
13390
+ };
13391
+ }
13392
+ var HTTP_STATUS_MESSAGES2;
13393
+ var init_util = __esm(() => {
13394
+ HTTP_STATUS_MESSAGES2 = {
13395
+ 400: "Bad Request",
13396
+ 401: "Unauthorized - check your API key",
13397
+ 403: "Forbidden - access denied",
13398
+ 404: "Not Found",
13399
+ 408: "Request Timeout",
13400
+ 429: "Too Many Requests - rate limited",
13401
+ 500: "Internal Server Error",
13402
+ 502: "Bad Gateway - server temporarily unavailable",
13403
+ 503: "Service Unavailable - server temporarily unavailable",
13404
+ 504: "Gateway Timeout - server temporarily unavailable"
13405
+ };
13406
+ });
13407
+
13049
13408
  // node_modules/commander/esm.mjs
13050
13409
  var import__ = __toESM(require_commander(), 1);
13051
13410
  var {
@@ -13064,7 +13423,7 @@ var {
13064
13423
  // package.json
13065
13424
  var package_default = {
13066
13425
  name: "postgresai",
13067
- version: "0.15.0-dev.1",
13426
+ version: "0.15.0-dev.10",
13068
13427
  description: "postgres_ai CLI",
13069
13428
  license: "Apache-2.0",
13070
13429
  private: false,
@@ -13092,7 +13451,7 @@ var package_default = {
13092
13451
  "embed-metrics": "bun run scripts/embed-metrics.ts",
13093
13452
  "embed-checkup-dictionary": "bun run scripts/embed-checkup-dictionary.ts",
13094
13453
  "embed-all": "bun run embed-metrics && bun run embed-checkup-dictionary",
13095
- build: `bun run embed-all && bun build ./bin/postgres-ai.ts --outdir ./dist/bin --target node && node -e "const fs=require('fs');const f='./dist/bin/postgres-ai.js';fs.writeFileSync(f,fs.readFileSync(f,'utf8').replace('#!/usr/bin/env bun','#!/usr/bin/env node'))" && cp -r ./sql ./dist/sql`,
13454
+ build: `bun run embed-all && bun build ./bin/postgres-ai.ts --outdir ./dist/bin --target node && node -e "const fs=require('fs');const f='./dist/bin/postgres-ai.js';fs.writeFileSync(f,fs.readFileSync(f,'utf8').replace('#!/usr/bin/env bun','#!/usr/bin/env node'))" && cp -r ./sql ./dist/sql && cp ../instances.demo.yml ./instances.demo.yml`,
13096
13455
  prepublishOnly: "npm run build",
13097
13456
  start: "bun ./bin/postgres-ai.ts --help",
13098
13457
  "start:node": "node ./dist/bin/postgres-ai.js --help",
@@ -13105,7 +13464,7 @@ var package_default = {
13105
13464
  },
13106
13465
  dependencies: {
13107
13466
  "@modelcontextprotocol/sdk": "^1.20.2",
13108
- commander: "^12.1.0",
13467
+ commander: "^14.0.3",
13109
13468
  "js-yaml": "^4.1.0",
13110
13469
  pg: "^8.16.3"
13111
13470
  },
@@ -13115,7 +13474,7 @@ var package_default = {
13115
13474
  "@types/pg": "^8.15.6",
13116
13475
  ajv: "^8.17.1",
13117
13476
  "ajv-formats": "^3.0.1",
13118
- typescript: "^5.9.3"
13477
+ typescript: "^6.0.2"
13119
13478
  },
13120
13479
  publishConfig: {
13121
13480
  access: "public"
@@ -13140,6 +13499,7 @@ function readConfig() {
13140
13499
  const config = {
13141
13500
  apiKey: null,
13142
13501
  baseUrl: null,
13502
+ storageBaseUrl: null,
13143
13503
  orgId: null,
13144
13504
  defaultProject: null,
13145
13505
  projectName: null
@@ -13151,6 +13511,7 @@ function readConfig() {
13151
13511
  const parsed = JSON.parse(content);
13152
13512
  config.apiKey = parsed.apiKey ?? null;
13153
13513
  config.baseUrl = parsed.baseUrl ?? null;
13514
+ config.storageBaseUrl = parsed.storageBaseUrl ?? null;
13154
13515
  config.orgId = parsed.orgId ?? null;
13155
13516
  config.defaultProject = parsed.defaultProject ?? null;
13156
13517
  config.projectName = parsed.projectName ?? null;
@@ -15873,9 +16234,10 @@ var safeLoadAll = renamed("safeLoadAll", "loadAll");
15873
16234
  var safeDump = renamed("safeDump", "dump");
15874
16235
 
15875
16236
  // bin/postgres-ai.ts
15876
- import * as fs5 from "fs";
15877
- import * as path5 from "path";
16237
+ import * as fs6 from "fs";
16238
+ import * as path6 from "path";
15878
16239
  import * as os3 from "os";
16240
+ import { fileURLToPath as fileURLToPath2 } from "url";
15879
16241
  import * as crypto2 from "crypto";
15880
16242
 
15881
16243
  // node_modules/pg/esm/index.mjs
@@ -15892,7 +16254,7 @@ var Result = import_lib.default.Result;
15892
16254
  var TypeOverrides = import_lib.default.TypeOverrides;
15893
16255
  var defaults = import_lib.default.defaults;
15894
16256
  // package.json
15895
- var version = "0.15.0-dev.1";
16257
+ var version = "0.15.0-dev.10";
15896
16258
  var package_default2 = {
15897
16259
  name: "postgresai",
15898
16260
  version,
@@ -15923,7 +16285,7 @@ var package_default2 = {
15923
16285
  "embed-metrics": "bun run scripts/embed-metrics.ts",
15924
16286
  "embed-checkup-dictionary": "bun run scripts/embed-checkup-dictionary.ts",
15925
16287
  "embed-all": "bun run embed-metrics && bun run embed-checkup-dictionary",
15926
- build: `bun run embed-all && bun build ./bin/postgres-ai.ts --outdir ./dist/bin --target node && node -e "const fs=require('fs');const f='./dist/bin/postgres-ai.js';fs.writeFileSync(f,fs.readFileSync(f,'utf8').replace('#!/usr/bin/env bun','#!/usr/bin/env node'))" && cp -r ./sql ./dist/sql`,
16288
+ build: `bun run embed-all && bun build ./bin/postgres-ai.ts --outdir ./dist/bin --target node && node -e "const fs=require('fs');const f='./dist/bin/postgres-ai.js';fs.writeFileSync(f,fs.readFileSync(f,'utf8').replace('#!/usr/bin/env bun','#!/usr/bin/env node'))" && cp -r ./sql ./dist/sql && cp ../instances.demo.yml ./instances.demo.yml`,
15927
16289
  prepublishOnly: "npm run build",
15928
16290
  start: "bun ./bin/postgres-ai.ts --help",
15929
16291
  "start:node": "node ./dist/bin/postgres-ai.js --help",
@@ -15936,7 +16298,7 @@ var package_default2 = {
15936
16298
  },
15937
16299
  dependencies: {
15938
16300
  "@modelcontextprotocol/sdk": "^1.20.2",
15939
- commander: "^12.1.0",
16301
+ commander: "^14.0.3",
15940
16302
  "js-yaml": "^4.1.0",
15941
16303
  pg: "^8.16.3"
15942
16304
  },
@@ -15946,7 +16308,7 @@ var package_default2 = {
15946
16308
  "@types/pg": "^8.15.6",
15947
16309
  ajv: "^8.17.1",
15948
16310
  "ajv-formats": "^3.0.1",
15949
- typescript: "^5.9.3"
16311
+ typescript: "^6.0.2"
15950
16312
  },
15951
16313
  publishConfig: {
15952
16314
  access: "public"
@@ -15971,6 +16333,7 @@ function readConfig2() {
15971
16333
  const config = {
15972
16334
  apiKey: null,
15973
16335
  baseUrl: null,
16336
+ storageBaseUrl: null,
15974
16337
  orgId: null,
15975
16338
  defaultProject: null,
15976
16339
  projectName: null
@@ -15982,6 +16345,7 @@ function readConfig2() {
15982
16345
  const parsed = JSON.parse(content);
15983
16346
  config.apiKey = parsed.apiKey ?? null;
15984
16347
  config.baseUrl = parsed.baseUrl ?? null;
16348
+ config.storageBaseUrl = parsed.storageBaseUrl ?? null;
15985
16349
  config.orgId = parsed.orgId ?? null;
15986
16350
  config.defaultProject = parsed.defaultProject ?? null;
15987
16351
  config.projectName = parsed.projectName ?? null;
@@ -16079,11 +16443,14 @@ function normalizeBaseUrl(value) {
16079
16443
  function resolveBaseUrls(opts, cfg, defaults2 = {}) {
16080
16444
  const defApi = defaults2.apiBaseUrl || "https://postgres.ai/api/general/";
16081
16445
  const defUi = defaults2.uiBaseUrl || "https://console.postgres.ai";
16446
+ const defStorage = defaults2.storageBaseUrl || "https://postgres.ai/storage";
16082
16447
  const apiCandidate = opts?.apiBaseUrl || process.env.PGAI_API_BASE_URL || cfg?.baseUrl || defApi;
16083
16448
  const uiCandidate = opts?.uiBaseUrl || process.env.PGAI_UI_BASE_URL || defUi;
16449
+ const storageCandidate = opts?.storageBaseUrl || process.env.PGAI_STORAGE_BASE_URL || cfg?.storageBaseUrl || defStorage;
16084
16450
  return {
16085
16451
  apiBaseUrl: normalizeBaseUrl(apiCandidate),
16086
- uiBaseUrl: normalizeBaseUrl(uiCandidate)
16452
+ uiBaseUrl: normalizeBaseUrl(uiCandidate),
16453
+ storageBaseUrl: normalizeBaseUrl(storageCandidate)
16087
16454
  };
16088
16455
  }
16089
16456
 
@@ -16115,18 +16482,18 @@ async function fetchIssues(params) {
16115
16482
  };
16116
16483
  if (debug) {
16117
16484
  const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
16118
- console.log(`Debug: Resolved API base URL: ${base}`);
16119
- console.log(`Debug: GET URL: ${url.toString()}`);
16120
- console.log(`Debug: Auth scheme: access-token`);
16121
- console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
16485
+ console.error(`Debug: Resolved API base URL: ${base}`);
16486
+ console.error(`Debug: GET URL: ${url.toString()}`);
16487
+ console.error(`Debug: Auth scheme: access-token`);
16488
+ console.error(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
16122
16489
  }
16123
16490
  const response = await fetch(url.toString(), {
16124
16491
  method: "GET",
16125
16492
  headers
16126
16493
  });
16127
16494
  if (debug) {
16128
- console.log(`Debug: Response status: ${response.status}`);
16129
- console.log(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
16495
+ console.error(`Debug: Response status: ${response.status}`);
16496
+ console.error(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
16130
16497
  }
16131
16498
  const data = await response.text();
16132
16499
  if (response.ok) {
@@ -16157,18 +16524,18 @@ async function fetchIssueComments(params) {
16157
16524
  };
16158
16525
  if (debug) {
16159
16526
  const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
16160
- console.log(`Debug: Resolved API base URL: ${base}`);
16161
- console.log(`Debug: GET URL: ${url.toString()}`);
16162
- console.log(`Debug: Auth scheme: access-token`);
16163
- console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
16527
+ console.error(`Debug: Resolved API base URL: ${base}`);
16528
+ console.error(`Debug: GET URL: ${url.toString()}`);
16529
+ console.error(`Debug: Auth scheme: access-token`);
16530
+ console.error(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
16164
16531
  }
16165
16532
  const response = await fetch(url.toString(), {
16166
16533
  method: "GET",
16167
16534
  headers
16168
16535
  });
16169
16536
  if (debug) {
16170
- console.log(`Debug: Response status: ${response.status}`);
16171
- console.log(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
16537
+ console.error(`Debug: Response status: ${response.status}`);
16538
+ console.error(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
16172
16539
  }
16173
16540
  const data = await response.text();
16174
16541
  if (response.ok) {
@@ -16202,18 +16569,18 @@ async function fetchIssue(params) {
16202
16569
  };
16203
16570
  if (debug) {
16204
16571
  const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
16205
- console.log(`Debug: Resolved API base URL: ${base}`);
16206
- console.log(`Debug: GET URL: ${url.toString()}`);
16207
- console.log(`Debug: Auth scheme: access-token`);
16208
- console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
16572
+ console.error(`Debug: Resolved API base URL: ${base}`);
16573
+ console.error(`Debug: GET URL: ${url.toString()}`);
16574
+ console.error(`Debug: Auth scheme: access-token`);
16575
+ console.error(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
16209
16576
  }
16210
16577
  const response = await fetch(url.toString(), {
16211
16578
  method: "GET",
16212
16579
  headers
16213
16580
  });
16214
16581
  if (debug) {
16215
- console.log(`Debug: Response status: ${response.status}`);
16216
- console.log(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
16582
+ console.error(`Debug: Response status: ${response.status}`);
16583
+ console.error(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
16217
16584
  }
16218
16585
  const data = await response.text();
16219
16586
  if (response.ok) {
@@ -16275,11 +16642,11 @@ async function createIssue(params) {
16275
16642
  };
16276
16643
  if (debug) {
16277
16644
  const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
16278
- console.log(`Debug: Resolved API base URL: ${base}`);
16279
- console.log(`Debug: POST URL: ${url.toString()}`);
16280
- console.log(`Debug: Auth scheme: access-token`);
16281
- console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
16282
- console.log(`Debug: Request body: ${body}`);
16645
+ console.error(`Debug: Resolved API base URL: ${base}`);
16646
+ console.error(`Debug: POST URL: ${url.toString()}`);
16647
+ console.error(`Debug: Auth scheme: access-token`);
16648
+ console.error(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
16649
+ console.error(`Debug: Request body: ${body}`);
16283
16650
  }
16284
16651
  const response = await fetch(url.toString(), {
16285
16652
  method: "POST",
@@ -16287,8 +16654,8 @@ async function createIssue(params) {
16287
16654
  body
16288
16655
  });
16289
16656
  if (debug) {
16290
- console.log(`Debug: Response status: ${response.status}`);
16291
- console.log(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
16657
+ console.error(`Debug: Response status: ${response.status}`);
16658
+ console.error(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
16292
16659
  }
16293
16660
  const data = await response.text();
16294
16661
  if (response.ok) {
@@ -16330,11 +16697,11 @@ async function createIssueComment(params) {
16330
16697
  };
16331
16698
  if (debug) {
16332
16699
  const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
16333
- console.log(`Debug: Resolved API base URL: ${base}`);
16334
- console.log(`Debug: POST URL: ${url.toString()}`);
16335
- console.log(`Debug: Auth scheme: access-token`);
16336
- console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
16337
- console.log(`Debug: Request body: ${body}`);
16700
+ console.error(`Debug: Resolved API base URL: ${base}`);
16701
+ console.error(`Debug: POST URL: ${url.toString()}`);
16702
+ console.error(`Debug: Auth scheme: access-token`);
16703
+ console.error(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
16704
+ console.error(`Debug: Request body: ${body}`);
16338
16705
  }
16339
16706
  const response = await fetch(url.toString(), {
16340
16707
  method: "POST",
@@ -16342,8 +16709,8 @@ async function createIssueComment(params) {
16342
16709
  body
16343
16710
  });
16344
16711
  if (debug) {
16345
- console.log(`Debug: Response status: ${response.status}`);
16346
- console.log(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
16712
+ console.error(`Debug: Response status: ${response.status}`);
16713
+ console.error(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
16347
16714
  }
16348
16715
  const data = await response.text();
16349
16716
  if (response.ok) {
@@ -16393,11 +16760,11 @@ async function updateIssue(params) {
16393
16760
  };
16394
16761
  if (debug) {
16395
16762
  const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
16396
- console.log(`Debug: Resolved API base URL: ${base}`);
16397
- console.log(`Debug: POST URL: ${url.toString()}`);
16398
- console.log(`Debug: Auth scheme: access-token`);
16399
- console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
16400
- console.log(`Debug: Request body: ${body}`);
16763
+ console.error(`Debug: Resolved API base URL: ${base}`);
16764
+ console.error(`Debug: POST URL: ${url.toString()}`);
16765
+ console.error(`Debug: Auth scheme: access-token`);
16766
+ console.error(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
16767
+ console.error(`Debug: Request body: ${body}`);
16401
16768
  }
16402
16769
  const response = await fetch(url.toString(), {
16403
16770
  method: "POST",
@@ -16405,8 +16772,8 @@ async function updateIssue(params) {
16405
16772
  body
16406
16773
  });
16407
16774
  if (debug) {
16408
- console.log(`Debug: Response status: ${response.status}`);
16409
- console.log(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
16775
+ console.error(`Debug: Response status: ${response.status}`);
16776
+ console.error(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
16410
16777
  }
16411
16778
  const data = await response.text();
16412
16779
  if (response.ok) {
@@ -16445,11 +16812,11 @@ async function updateIssueComment(params) {
16445
16812
  };
16446
16813
  if (debug) {
16447
16814
  const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
16448
- console.log(`Debug: Resolved API base URL: ${base}`);
16449
- console.log(`Debug: POST URL: ${url.toString()}`);
16450
- console.log(`Debug: Auth scheme: access-token`);
16451
- console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
16452
- console.log(`Debug: Request body: ${body}`);
16815
+ console.error(`Debug: Resolved API base URL: ${base}`);
16816
+ console.error(`Debug: POST URL: ${url.toString()}`);
16817
+ console.error(`Debug: Auth scheme: access-token`);
16818
+ console.error(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
16819
+ console.error(`Debug: Request body: ${body}`);
16453
16820
  }
16454
16821
  const response = await fetch(url.toString(), {
16455
16822
  method: "POST",
@@ -16457,8 +16824,8 @@ async function updateIssueComment(params) {
16457
16824
  body
16458
16825
  });
16459
16826
  if (debug) {
16460
- console.log(`Debug: Response status: ${response.status}`);
16461
- console.log(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
16827
+ console.error(`Debug: Response status: ${response.status}`);
16828
+ console.error(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
16462
16829
  }
16463
16830
  const data = await response.text();
16464
16831
  if (response.ok) {
@@ -16497,18 +16864,18 @@ async function fetchActionItem(params) {
16497
16864
  };
16498
16865
  if (debug) {
16499
16866
  const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
16500
- console.log(`Debug: Resolved API base URL: ${base}`);
16501
- console.log(`Debug: GET URL: ${url.toString()}`);
16502
- console.log(`Debug: Auth scheme: access-token`);
16503
- console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
16867
+ console.error(`Debug: Resolved API base URL: ${base}`);
16868
+ console.error(`Debug: GET URL: ${url.toString()}`);
16869
+ console.error(`Debug: Auth scheme: access-token`);
16870
+ console.error(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
16504
16871
  }
16505
16872
  const response = await fetch(url.toString(), {
16506
16873
  method: "GET",
16507
16874
  headers
16508
16875
  });
16509
16876
  if (debug) {
16510
- console.log(`Debug: Response status: ${response.status}`);
16511
- console.log(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
16877
+ console.error(`Debug: Response status: ${response.status}`);
16878
+ console.error(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
16512
16879
  }
16513
16880
  const data = await response.text();
16514
16881
  if (response.ok) {
@@ -16548,18 +16915,18 @@ async function fetchActionItems(params) {
16548
16915
  };
16549
16916
  if (debug) {
16550
16917
  const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
16551
- console.log(`Debug: Resolved API base URL: ${base}`);
16552
- console.log(`Debug: GET URL: ${url.toString()}`);
16553
- console.log(`Debug: Auth scheme: access-token`);
16554
- console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
16918
+ console.error(`Debug: Resolved API base URL: ${base}`);
16919
+ console.error(`Debug: GET URL: ${url.toString()}`);
16920
+ console.error(`Debug: Auth scheme: access-token`);
16921
+ console.error(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
16555
16922
  }
16556
16923
  const response = await fetch(url.toString(), {
16557
16924
  method: "GET",
16558
16925
  headers
16559
16926
  });
16560
16927
  if (debug) {
16561
- console.log(`Debug: Response status: ${response.status}`);
16562
- console.log(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
16928
+ console.error(`Debug: Response status: ${response.status}`);
16929
+ console.error(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
16563
16930
  }
16564
16931
  const data = await response.text();
16565
16932
  if (response.ok) {
@@ -16611,11 +16978,11 @@ async function createActionItem(params) {
16611
16978
  };
16612
16979
  if (debug) {
16613
16980
  const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
16614
- console.log(`Debug: Resolved API base URL: ${base}`);
16615
- console.log(`Debug: POST URL: ${url.toString()}`);
16616
- console.log(`Debug: Auth scheme: access-token`);
16617
- console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
16618
- console.log(`Debug: Request body: ${body}`);
16981
+ console.error(`Debug: Resolved API base URL: ${base}`);
16982
+ console.error(`Debug: POST URL: ${url.toString()}`);
16983
+ console.error(`Debug: Auth scheme: access-token`);
16984
+ console.error(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
16985
+ console.error(`Debug: Request body: ${body}`);
16619
16986
  }
16620
16987
  const response = await fetch(url.toString(), {
16621
16988
  method: "POST",
@@ -16623,8 +16990,8 @@ async function createActionItem(params) {
16623
16990
  body
16624
16991
  });
16625
16992
  if (debug) {
16626
- console.log(`Debug: Response status: ${response.status}`);
16627
- console.log(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
16993
+ console.error(`Debug: Response status: ${response.status}`);
16994
+ console.error(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
16628
16995
  }
16629
16996
  const data = await response.text();
16630
16997
  if (response.ok) {
@@ -16688,11 +17055,11 @@ async function updateActionItem(params) {
16688
17055
  };
16689
17056
  if (debug) {
16690
17057
  const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
16691
- console.log(`Debug: Resolved API base URL: ${base}`);
16692
- console.log(`Debug: POST URL: ${url.toString()}`);
16693
- console.log(`Debug: Auth scheme: access-token`);
16694
- console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
16695
- console.log(`Debug: Request body: ${body}`);
17058
+ console.error(`Debug: Resolved API base URL: ${base}`);
17059
+ console.error(`Debug: POST URL: ${url.toString()}`);
17060
+ console.error(`Debug: Auth scheme: access-token`);
17061
+ console.error(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
17062
+ console.error(`Debug: Request body: ${body}`);
16696
17063
  }
16697
17064
  const response = await fetch(url.toString(), {
16698
17065
  method: "POST",
@@ -16700,8 +17067,8 @@ async function updateActionItem(params) {
16700
17067
  body
16701
17068
  });
16702
17069
  if (debug) {
16703
- console.log(`Debug: Response status: ${response.status}`);
16704
- console.log(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
17070
+ console.error(`Debug: Response status: ${response.status}`);
17071
+ console.error(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
16705
17072
  }
16706
17073
  if (!response.ok) {
16707
17074
  const data = await response.text();
@@ -16709,6 +17076,192 @@ async function updateActionItem(params) {
16709
17076
  }
16710
17077
  }
16711
17078
 
17079
+ // lib/reports.ts
17080
+ function parseFlexibleDate(input) {
17081
+ const s = input.trim();
17082
+ const dotMatch = s.match(/^(\d{1,2})\.(\d{1,2})\.(\d{4})(?:\s+(\d{1,2}):(\d{2})(?::(\d{2}))?)?$/);
17083
+ if (dotMatch) {
17084
+ const [, dd, mm, yyyy, hh, min, ss] = dotMatch;
17085
+ const iso = `${yyyy}-${mm.padStart(2, "0")}-${dd.padStart(2, "0")}T${(hh ?? "00").padStart(2, "0")}:${(min ?? "00").padStart(2, "0")}:${(ss ?? "00").padStart(2, "0")}Z`;
17086
+ const d = new Date(iso);
17087
+ if (isNaN(d.getTime()))
17088
+ throw new Error(`Invalid date: ${input}`);
17089
+ return d.toISOString();
17090
+ }
17091
+ const isoMatch = s.match(/^(\d{4})-(\d{2})-(\d{2})(?:[T ](\d{2}):(\d{2})(?::(\d{2}))?)?$/);
17092
+ if (isoMatch) {
17093
+ const [, yyyy, mm, dd, hh, min, ss] = isoMatch;
17094
+ const iso = `${yyyy}-${mm}-${dd}T${hh ?? "00"}:${min ?? "00"}:${ss ?? "00"}Z`;
17095
+ const d = new Date(iso);
17096
+ if (isNaN(d.getTime()))
17097
+ throw new Error(`Invalid date: ${input}`);
17098
+ return d.toISOString();
17099
+ }
17100
+ throw new Error(`Unrecognized date format: ${input}. Use YYYY-MM-DD or DD.MM.YYYY`);
17101
+ }
17102
+ async function fetchReports(params) {
17103
+ const { apiKey, apiBaseUrl, projectId, status, limit = 20, beforeDate, beforeId, debug } = params;
17104
+ if (!apiKey) {
17105
+ throw new Error("API key is required");
17106
+ }
17107
+ const base = normalizeBaseUrl(apiBaseUrl);
17108
+ const url = new URL(`${base}/checkup_reports`);
17109
+ url.searchParams.set("order", "id.desc");
17110
+ url.searchParams.set("limit", String(limit));
17111
+ if (typeof projectId === "number") {
17112
+ url.searchParams.set("project_id", `eq.${projectId}`);
17113
+ }
17114
+ if (status) {
17115
+ url.searchParams.set("status", `eq.${status}`);
17116
+ }
17117
+ if (beforeDate) {
17118
+ url.searchParams.set("created_at", `lt.${beforeDate}`);
17119
+ }
17120
+ if (typeof beforeId === "number") {
17121
+ url.searchParams.set("id", `lt.${beforeId}`);
17122
+ }
17123
+ const headers = {
17124
+ "access-token": apiKey,
17125
+ Prefer: "return=representation",
17126
+ "Content-Type": "application/json",
17127
+ Connection: "close"
17128
+ };
17129
+ if (debug) {
17130
+ const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
17131
+ console.error(`Debug: Resolved API base URL: ${base}`);
17132
+ console.error(`Debug: GET URL: ${url.toString()}`);
17133
+ console.error(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
17134
+ }
17135
+ const response = await fetch(url.toString(), { method: "GET", headers });
17136
+ if (debug) {
17137
+ console.error(`Debug: Response status: ${response.status}`);
17138
+ }
17139
+ const data = await response.text();
17140
+ if (response.ok) {
17141
+ try {
17142
+ return JSON.parse(data);
17143
+ } catch {
17144
+ throw new Error(`Failed to parse reports response: ${data}`);
17145
+ }
17146
+ } else {
17147
+ throw new Error(formatHttpError("Failed to fetch reports", response.status, data));
17148
+ }
17149
+ }
17150
+ var MAX_ALL_REPORTS = 1e4;
17151
+ async function fetchAllReports(params) {
17152
+ const pageSize = params.limit ?? 100;
17153
+ const all = [];
17154
+ let beforeId;
17155
+ while (true) {
17156
+ const page = await fetchReports({ ...params, limit: pageSize, beforeId });
17157
+ if (page.length === 0)
17158
+ break;
17159
+ all.push(...page);
17160
+ if (all.length >= MAX_ALL_REPORTS) {
17161
+ console.warn(`Warning: reached maximum of ${MAX_ALL_REPORTS} reports, stopping pagination`);
17162
+ break;
17163
+ }
17164
+ beforeId = page[page.length - 1].id;
17165
+ if (page.length < pageSize)
17166
+ break;
17167
+ }
17168
+ return all;
17169
+ }
17170
+ async function fetchReportFiles(params) {
17171
+ const { apiKey, apiBaseUrl, reportId, type: type2, checkId, debug } = params;
17172
+ if (!apiKey) {
17173
+ throw new Error("API key is required");
17174
+ }
17175
+ if (reportId === undefined && !checkId) {
17176
+ throw new Error("Either reportId or checkId is required");
17177
+ }
17178
+ const base = normalizeBaseUrl(apiBaseUrl);
17179
+ const url = new URL(`${base}/checkup_report_files`);
17180
+ if (typeof reportId === "number") {
17181
+ url.searchParams.set("checkup_report_id", `eq.${reportId}`);
17182
+ }
17183
+ url.searchParams.set("order", "id.asc");
17184
+ if (type2) {
17185
+ url.searchParams.set("type", `eq.${type2}`);
17186
+ }
17187
+ if (checkId) {
17188
+ url.searchParams.set("check_id", `eq.${checkId}`);
17189
+ }
17190
+ const headers = {
17191
+ "access-token": apiKey,
17192
+ Prefer: "return=representation",
17193
+ "Content-Type": "application/json",
17194
+ Connection: "close"
17195
+ };
17196
+ if (debug) {
17197
+ const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
17198
+ console.error(`Debug: Resolved API base URL: ${base}`);
17199
+ console.error(`Debug: GET URL: ${url.toString()}`);
17200
+ console.error(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
17201
+ }
17202
+ const response = await fetch(url.toString(), { method: "GET", headers });
17203
+ if (debug) {
17204
+ console.error(`Debug: Response status: ${response.status}`);
17205
+ }
17206
+ const data = await response.text();
17207
+ if (response.ok) {
17208
+ try {
17209
+ return JSON.parse(data);
17210
+ } catch {
17211
+ throw new Error(`Failed to parse report files response: ${data}`);
17212
+ }
17213
+ } else {
17214
+ throw new Error(formatHttpError("Failed to fetch report files", response.status, data));
17215
+ }
17216
+ }
17217
+ async function fetchReportFileData(params) {
17218
+ const { apiKey, apiBaseUrl, reportId, type: type2, checkId, debug } = params;
17219
+ if (!apiKey) {
17220
+ throw new Error("API key is required");
17221
+ }
17222
+ if (reportId === undefined && !checkId) {
17223
+ throw new Error("Either reportId or checkId is required");
17224
+ }
17225
+ const base = normalizeBaseUrl(apiBaseUrl);
17226
+ const url = new URL(`${base}/checkup_report_file_data`);
17227
+ if (typeof reportId === "number") {
17228
+ url.searchParams.set("checkup_report_id", `eq.${reportId}`);
17229
+ }
17230
+ url.searchParams.set("order", "id.asc");
17231
+ if (type2) {
17232
+ url.searchParams.set("type", `eq.${type2}`);
17233
+ }
17234
+ if (checkId) {
17235
+ url.searchParams.set("check_id", `eq.${checkId}`);
17236
+ }
17237
+ const headers = {
17238
+ "access-token": apiKey,
17239
+ Prefer: "return=representation",
17240
+ "Content-Type": "application/json",
17241
+ Connection: "close"
17242
+ };
17243
+ if (debug) {
17244
+ const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
17245
+ console.error(`Debug: Resolved API base URL: ${base}`);
17246
+ console.error(`Debug: GET URL: ${url.toString()}`);
17247
+ console.error(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
17248
+ }
17249
+ const response = await fetch(url.toString(), { method: "GET", headers });
17250
+ if (debug) {
17251
+ console.error(`Debug: Response status: ${response.status}`);
17252
+ }
17253
+ const data = await response.text();
17254
+ if (response.ok) {
17255
+ try {
17256
+ return JSON.parse(data);
17257
+ } catch {
17258
+ throw new Error(`Failed to parse report file data response: ${data}`);
17259
+ }
17260
+ } else {
17261
+ throw new Error(formatHttpError("Failed to fetch report file data", response.status, data));
17262
+ }
17263
+ }
17264
+
16712
17265
  // node_modules/zod/v4/core/core.js
16713
17266
  var NEVER = Object.freeze({
16714
17267
  status: "aborted"
@@ -23770,9 +24323,49 @@ async function handleToolCall(req, rootOpts, extra) {
23770
24323
  await updateActionItem({ apiKey, apiBaseUrl, actionItemId, title, description, isDone, status, statusReason, debug });
23771
24324
  return { content: [{ type: "text", text: JSON.stringify({ success: true }, null, 2) }] };
23772
24325
  }
23773
- throw new Error(`Unknown tool: ${toolName}`);
23774
- } catch (err) {
23775
- const message = err instanceof Error ? err.message : String(err);
24326
+ if (toolName === "list_reports") {
24327
+ const projectId = args.project_id !== undefined ? Number(args.project_id) : undefined;
24328
+ const status = args.status ? String(args.status) : undefined;
24329
+ const limit = args.limit !== undefined ? Number(args.limit) : undefined;
24330
+ const beforeDate = args.before_date ? parseFlexibleDate(String(args.before_date)) : undefined;
24331
+ const all = args.all === true;
24332
+ let reports;
24333
+ if (all) {
24334
+ reports = await fetchAllReports({ apiKey, apiBaseUrl, projectId, status, limit, debug });
24335
+ } else {
24336
+ reports = await fetchReports({ apiKey, apiBaseUrl, projectId, status, limit, beforeDate, debug });
24337
+ }
24338
+ return { content: [{ type: "text", text: JSON.stringify(reports, null, 2) }] };
24339
+ }
24340
+ if (toolName === "list_report_files") {
24341
+ const reportId = args.report_id !== undefined ? Number(args.report_id) : undefined;
24342
+ if (reportId !== undefined && isNaN(reportId)) {
24343
+ return { content: [{ type: "text", text: "report_id must be a number" }], isError: true };
24344
+ }
24345
+ const type2 = args.type ? String(args.type) : undefined;
24346
+ const checkId = args.check_id ? String(args.check_id) : undefined;
24347
+ if (reportId === undefined && !checkId) {
24348
+ return { content: [{ type: "text", text: "Either report_id or check_id is required" }], isError: true };
24349
+ }
24350
+ const files = await fetchReportFiles({ apiKey, apiBaseUrl, reportId, type: type2, checkId, debug });
24351
+ return { content: [{ type: "text", text: JSON.stringify(files, null, 2) }] };
24352
+ }
24353
+ if (toolName === "get_report_data") {
24354
+ const reportId = args.report_id !== undefined ? Number(args.report_id) : undefined;
24355
+ if (reportId !== undefined && isNaN(reportId)) {
24356
+ return { content: [{ type: "text", text: "report_id must be a number" }], isError: true };
24357
+ }
24358
+ const type2 = args.type ? String(args.type) : undefined;
24359
+ const checkId = args.check_id ? String(args.check_id) : undefined;
24360
+ if (reportId === undefined && !checkId) {
24361
+ return { content: [{ type: "text", text: "Either report_id or check_id is required" }], isError: true };
24362
+ }
24363
+ const files = await fetchReportFileData({ apiKey, apiBaseUrl, reportId, type: type2, checkId, debug });
24364
+ return { content: [{ type: "text", text: JSON.stringify(files, null, 2) }] };
24365
+ }
24366
+ throw new Error(`Unknown tool: ${toolName}`);
24367
+ } catch (err) {
24368
+ const message = err instanceof Error ? err.message : String(err);
23776
24369
  return { content: [{ type: "text", text: message }], isError: true };
23777
24370
  }
23778
24371
  }
@@ -23955,6 +24548,50 @@ async function startMcpServer(rootOpts, extra) {
23955
24548
  required: ["action_item_id"],
23956
24549
  additionalProperties: false
23957
24550
  }
24551
+ },
24552
+ {
24553
+ name: "list_reports",
24554
+ description: "List checkup reports. Returns report metadata: id, project, status, timestamps. Use get_report_data to fetch actual report content. Supports date-based filtering with before_date.",
24555
+ inputSchema: {
24556
+ type: "object",
24557
+ properties: {
24558
+ project_id: { type: "number", description: "Filter by project ID" },
24559
+ status: { type: "string", description: "Filter by status (e.g., 'completed')" },
24560
+ limit: { type: "number", description: "Max number of reports to return (default: 20)" },
24561
+ before_date: { type: "string", description: "Show reports created before this date (YYYY-MM-DD, DD.MM.YYYY, YYYY-MM-DD HH:mm, etc.)" },
24562
+ all: { type: "boolean", description: "Fetch all reports (paginated automatically)" },
24563
+ debug: { type: "boolean", description: "Enable verbose debug logs" }
24564
+ },
24565
+ additionalProperties: false
24566
+ }
24567
+ },
24568
+ {
24569
+ name: "list_report_files",
24570
+ description: "List files in a checkup report (metadata only, no content). Each report contains json (raw data) and md (markdown analysis) files per check. Either report_id or check_id must be provided.",
24571
+ inputSchema: {
24572
+ type: "object",
24573
+ properties: {
24574
+ report_id: { type: "number", description: "Checkup report ID (optional if check_id is provided)" },
24575
+ type: { type: "string", description: "Filter by file type: 'json' or 'md'" },
24576
+ check_id: { type: "string", description: "Filter by check ID (e.g., 'H002', 'F004')" },
24577
+ debug: { type: "boolean", description: "Enable verbose debug logs" }
24578
+ },
24579
+ additionalProperties: false
24580
+ }
24581
+ },
24582
+ {
24583
+ name: "get_report_data",
24584
+ description: "Get checkup report file content. Returns files with a 'data' field containing the actual content: markdown analysis or JSON raw data. Use type='md' for human-readable analysis with recommendations, type='json' for raw check data. Either report_id or check_id must be provided.",
24585
+ inputSchema: {
24586
+ type: "object",
24587
+ properties: {
24588
+ report_id: { type: "number", description: "Checkup report ID (optional if check_id is provided)" },
24589
+ type: { type: "string", description: "Filter by file type: 'json' for raw data, 'md' for markdown analysis" },
24590
+ check_id: { type: "string", description: "Filter by check ID (e.g., 'H002', 'F004')" },
24591
+ debug: { type: "boolean", description: "Enable verbose debug logs" }
24592
+ },
24593
+ additionalProperties: false
24594
+ }
23958
24595
  }
23959
24596
  ]
23960
24597
  };
@@ -23994,18 +24631,18 @@ async function fetchIssues2(params) {
23994
24631
  };
23995
24632
  if (debug) {
23996
24633
  const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
23997
- console.log(`Debug: Resolved API base URL: ${base}`);
23998
- console.log(`Debug: GET URL: ${url.toString()}`);
23999
- console.log(`Debug: Auth scheme: access-token`);
24000
- console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
24634
+ console.error(`Debug: Resolved API base URL: ${base}`);
24635
+ console.error(`Debug: GET URL: ${url.toString()}`);
24636
+ console.error(`Debug: Auth scheme: access-token`);
24637
+ console.error(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
24001
24638
  }
24002
24639
  const response = await fetch(url.toString(), {
24003
24640
  method: "GET",
24004
24641
  headers
24005
24642
  });
24006
24643
  if (debug) {
24007
- console.log(`Debug: Response status: ${response.status}`);
24008
- console.log(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
24644
+ console.error(`Debug: Response status: ${response.status}`);
24645
+ console.error(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
24009
24646
  }
24010
24647
  const data = await response.text();
24011
24648
  if (response.ok) {
@@ -24036,18 +24673,18 @@ async function fetchIssueComments2(params) {
24036
24673
  };
24037
24674
  if (debug) {
24038
24675
  const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
24039
- console.log(`Debug: Resolved API base URL: ${base}`);
24040
- console.log(`Debug: GET URL: ${url.toString()}`);
24041
- console.log(`Debug: Auth scheme: access-token`);
24042
- console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
24676
+ console.error(`Debug: Resolved API base URL: ${base}`);
24677
+ console.error(`Debug: GET URL: ${url.toString()}`);
24678
+ console.error(`Debug: Auth scheme: access-token`);
24679
+ console.error(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
24043
24680
  }
24044
24681
  const response = await fetch(url.toString(), {
24045
24682
  method: "GET",
24046
24683
  headers
24047
24684
  });
24048
24685
  if (debug) {
24049
- console.log(`Debug: Response status: ${response.status}`);
24050
- console.log(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
24686
+ console.error(`Debug: Response status: ${response.status}`);
24687
+ console.error(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
24051
24688
  }
24052
24689
  const data = await response.text();
24053
24690
  if (response.ok) {
@@ -24081,18 +24718,18 @@ async function fetchIssue2(params) {
24081
24718
  };
24082
24719
  if (debug) {
24083
24720
  const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
24084
- console.log(`Debug: Resolved API base URL: ${base}`);
24085
- console.log(`Debug: GET URL: ${url.toString()}`);
24086
- console.log(`Debug: Auth scheme: access-token`);
24087
- console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
24721
+ console.error(`Debug: Resolved API base URL: ${base}`);
24722
+ console.error(`Debug: GET URL: ${url.toString()}`);
24723
+ console.error(`Debug: Auth scheme: access-token`);
24724
+ console.error(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
24088
24725
  }
24089
24726
  const response = await fetch(url.toString(), {
24090
24727
  method: "GET",
24091
24728
  headers
24092
24729
  });
24093
24730
  if (debug) {
24094
- console.log(`Debug: Response status: ${response.status}`);
24095
- console.log(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
24731
+ console.error(`Debug: Response status: ${response.status}`);
24732
+ console.error(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
24096
24733
  }
24097
24734
  const data = await response.text();
24098
24735
  if (response.ok) {
@@ -24154,11 +24791,11 @@ async function createIssue2(params) {
24154
24791
  };
24155
24792
  if (debug) {
24156
24793
  const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
24157
- console.log(`Debug: Resolved API base URL: ${base}`);
24158
- console.log(`Debug: POST URL: ${url.toString()}`);
24159
- console.log(`Debug: Auth scheme: access-token`);
24160
- console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
24161
- console.log(`Debug: Request body: ${body}`);
24794
+ console.error(`Debug: Resolved API base URL: ${base}`);
24795
+ console.error(`Debug: POST URL: ${url.toString()}`);
24796
+ console.error(`Debug: Auth scheme: access-token`);
24797
+ console.error(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
24798
+ console.error(`Debug: Request body: ${body}`);
24162
24799
  }
24163
24800
  const response = await fetch(url.toString(), {
24164
24801
  method: "POST",
@@ -24166,8 +24803,8 @@ async function createIssue2(params) {
24166
24803
  body
24167
24804
  });
24168
24805
  if (debug) {
24169
- console.log(`Debug: Response status: ${response.status}`);
24170
- console.log(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
24806
+ console.error(`Debug: Response status: ${response.status}`);
24807
+ console.error(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
24171
24808
  }
24172
24809
  const data = await response.text();
24173
24810
  if (response.ok) {
@@ -24209,11 +24846,11 @@ async function createIssueComment2(params) {
24209
24846
  };
24210
24847
  if (debug) {
24211
24848
  const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
24212
- console.log(`Debug: Resolved API base URL: ${base}`);
24213
- console.log(`Debug: POST URL: ${url.toString()}`);
24214
- console.log(`Debug: Auth scheme: access-token`);
24215
- console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
24216
- console.log(`Debug: Request body: ${body}`);
24849
+ console.error(`Debug: Resolved API base URL: ${base}`);
24850
+ console.error(`Debug: POST URL: ${url.toString()}`);
24851
+ console.error(`Debug: Auth scheme: access-token`);
24852
+ console.error(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
24853
+ console.error(`Debug: Request body: ${body}`);
24217
24854
  }
24218
24855
  const response = await fetch(url.toString(), {
24219
24856
  method: "POST",
@@ -24221,8 +24858,8 @@ async function createIssueComment2(params) {
24221
24858
  body
24222
24859
  });
24223
24860
  if (debug) {
24224
- console.log(`Debug: Response status: ${response.status}`);
24225
- console.log(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
24861
+ console.error(`Debug: Response status: ${response.status}`);
24862
+ console.error(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
24226
24863
  }
24227
24864
  const data = await response.text();
24228
24865
  if (response.ok) {
@@ -24272,11 +24909,11 @@ async function updateIssue2(params) {
24272
24909
  };
24273
24910
  if (debug) {
24274
24911
  const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
24275
- console.log(`Debug: Resolved API base URL: ${base}`);
24276
- console.log(`Debug: POST URL: ${url.toString()}`);
24277
- console.log(`Debug: Auth scheme: access-token`);
24278
- console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
24279
- console.log(`Debug: Request body: ${body}`);
24912
+ console.error(`Debug: Resolved API base URL: ${base}`);
24913
+ console.error(`Debug: POST URL: ${url.toString()}`);
24914
+ console.error(`Debug: Auth scheme: access-token`);
24915
+ console.error(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
24916
+ console.error(`Debug: Request body: ${body}`);
24280
24917
  }
24281
24918
  const response = await fetch(url.toString(), {
24282
24919
  method: "POST",
@@ -24284,8 +24921,8 @@ async function updateIssue2(params) {
24284
24921
  body
24285
24922
  });
24286
24923
  if (debug) {
24287
- console.log(`Debug: Response status: ${response.status}`);
24288
- console.log(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
24924
+ console.error(`Debug: Response status: ${response.status}`);
24925
+ console.error(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
24289
24926
  }
24290
24927
  const data = await response.text();
24291
24928
  if (response.ok) {
@@ -24324,11 +24961,11 @@ async function updateIssueComment2(params) {
24324
24961
  };
24325
24962
  if (debug) {
24326
24963
  const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
24327
- console.log(`Debug: Resolved API base URL: ${base}`);
24328
- console.log(`Debug: POST URL: ${url.toString()}`);
24329
- console.log(`Debug: Auth scheme: access-token`);
24330
- console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
24331
- console.log(`Debug: Request body: ${body}`);
24964
+ console.error(`Debug: Resolved API base URL: ${base}`);
24965
+ console.error(`Debug: POST URL: ${url.toString()}`);
24966
+ console.error(`Debug: Auth scheme: access-token`);
24967
+ console.error(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
24968
+ console.error(`Debug: Request body: ${body}`);
24332
24969
  }
24333
24970
  const response = await fetch(url.toString(), {
24334
24971
  method: "POST",
@@ -24336,8 +24973,8 @@ async function updateIssueComment2(params) {
24336
24973
  body
24337
24974
  });
24338
24975
  if (debug) {
24339
- console.log(`Debug: Response status: ${response.status}`);
24340
- console.log(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
24976
+ console.error(`Debug: Response status: ${response.status}`);
24977
+ console.error(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
24341
24978
  }
24342
24979
  const data = await response.text();
24343
24980
  if (response.ok) {
@@ -24376,18 +25013,18 @@ async function fetchActionItem2(params) {
24376
25013
  };
24377
25014
  if (debug) {
24378
25015
  const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
24379
- console.log(`Debug: Resolved API base URL: ${base}`);
24380
- console.log(`Debug: GET URL: ${url.toString()}`);
24381
- console.log(`Debug: Auth scheme: access-token`);
24382
- console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
25016
+ console.error(`Debug: Resolved API base URL: ${base}`);
25017
+ console.error(`Debug: GET URL: ${url.toString()}`);
25018
+ console.error(`Debug: Auth scheme: access-token`);
25019
+ console.error(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
24383
25020
  }
24384
25021
  const response = await fetch(url.toString(), {
24385
25022
  method: "GET",
24386
25023
  headers
24387
25024
  });
24388
25025
  if (debug) {
24389
- console.log(`Debug: Response status: ${response.status}`);
24390
- console.log(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
25026
+ console.error(`Debug: Response status: ${response.status}`);
25027
+ console.error(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
24391
25028
  }
24392
25029
  const data = await response.text();
24393
25030
  if (response.ok) {
@@ -24427,18 +25064,18 @@ async function fetchActionItems2(params) {
24427
25064
  };
24428
25065
  if (debug) {
24429
25066
  const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
24430
- console.log(`Debug: Resolved API base URL: ${base}`);
24431
- console.log(`Debug: GET URL: ${url.toString()}`);
24432
- console.log(`Debug: Auth scheme: access-token`);
24433
- console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
25067
+ console.error(`Debug: Resolved API base URL: ${base}`);
25068
+ console.error(`Debug: GET URL: ${url.toString()}`);
25069
+ console.error(`Debug: Auth scheme: access-token`);
25070
+ console.error(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
24434
25071
  }
24435
25072
  const response = await fetch(url.toString(), {
24436
25073
  method: "GET",
24437
25074
  headers
24438
25075
  });
24439
25076
  if (debug) {
24440
- console.log(`Debug: Response status: ${response.status}`);
24441
- console.log(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
25077
+ console.error(`Debug: Response status: ${response.status}`);
25078
+ console.error(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
24442
25079
  }
24443
25080
  const data = await response.text();
24444
25081
  if (response.ok) {
@@ -24490,11 +25127,11 @@ async function createActionItem2(params) {
24490
25127
  };
24491
25128
  if (debug) {
24492
25129
  const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
24493
- console.log(`Debug: Resolved API base URL: ${base}`);
24494
- console.log(`Debug: POST URL: ${url.toString()}`);
24495
- console.log(`Debug: Auth scheme: access-token`);
24496
- console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
24497
- console.log(`Debug: Request body: ${body}`);
25130
+ console.error(`Debug: Resolved API base URL: ${base}`);
25131
+ console.error(`Debug: POST URL: ${url.toString()}`);
25132
+ console.error(`Debug: Auth scheme: access-token`);
25133
+ console.error(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
25134
+ console.error(`Debug: Request body: ${body}`);
24498
25135
  }
24499
25136
  const response = await fetch(url.toString(), {
24500
25137
  method: "POST",
@@ -24502,8 +25139,8 @@ async function createActionItem2(params) {
24502
25139
  body
24503
25140
  });
24504
25141
  if (debug) {
24505
- console.log(`Debug: Response status: ${response.status}`);
24506
- console.log(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
25142
+ console.error(`Debug: Response status: ${response.status}`);
25143
+ console.error(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
24507
25144
  }
24508
25145
  const data = await response.text();
24509
25146
  if (response.ok) {
@@ -24567,11 +25204,11 @@ async function updateActionItem2(params) {
24567
25204
  };
24568
25205
  if (debug) {
24569
25206
  const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
24570
- console.log(`Debug: Resolved API base URL: ${base}`);
24571
- console.log(`Debug: POST URL: ${url.toString()}`);
24572
- console.log(`Debug: Auth scheme: access-token`);
24573
- console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
24574
- console.log(`Debug: Request body: ${body}`);
25207
+ console.error(`Debug: Resolved API base URL: ${base}`);
25208
+ console.error(`Debug: POST URL: ${url.toString()}`);
25209
+ console.error(`Debug: Auth scheme: access-token`);
25210
+ console.error(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
25211
+ console.error(`Debug: Request body: ${body}`);
24575
25212
  }
24576
25213
  const response = await fetch(url.toString(), {
24577
25214
  method: "POST",
@@ -24579,8 +25216,8 @@ async function updateActionItem2(params) {
24579
25216
  body
24580
25217
  });
24581
25218
  if (debug) {
24582
- console.log(`Debug: Response status: ${response.status}`);
24583
- console.log(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
25219
+ console.error(`Debug: Response status: ${response.status}`);
25220
+ console.error(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
24584
25221
  }
24585
25222
  if (!response.ok) {
24586
25223
  const data = await response.text();
@@ -24588,41 +25225,437 @@ async function updateActionItem2(params) {
24588
25225
  }
24589
25226
  }
24590
25227
 
24591
- // lib/util.ts
24592
- function maskSecret2(secret) {
24593
- if (!secret)
25228
+ // lib/reports.ts
25229
+ function parseFlexibleDate2(input) {
25230
+ const s = input.trim();
25231
+ const dotMatch = s.match(/^(\d{1,2})\.(\d{1,2})\.(\d{4})(?:\s+(\d{1,2}):(\d{2})(?::(\d{2}))?)?$/);
25232
+ if (dotMatch) {
25233
+ const [, dd, mm, yyyy, hh, min, ss] = dotMatch;
25234
+ const iso = `${yyyy}-${mm.padStart(2, "0")}-${dd.padStart(2, "0")}T${(hh ?? "00").padStart(2, "0")}:${(min ?? "00").padStart(2, "0")}:${(ss ?? "00").padStart(2, "0")}Z`;
25235
+ const d = new Date(iso);
25236
+ if (isNaN(d.getTime()))
25237
+ throw new Error(`Invalid date: ${input}`);
25238
+ return d.toISOString();
25239
+ }
25240
+ const isoMatch = s.match(/^(\d{4})-(\d{2})-(\d{2})(?:[T ](\d{2}):(\d{2})(?::(\d{2}))?)?$/);
25241
+ if (isoMatch) {
25242
+ const [, yyyy, mm, dd, hh, min, ss] = isoMatch;
25243
+ const iso = `${yyyy}-${mm}-${dd}T${hh ?? "00"}:${min ?? "00"}:${ss ?? "00"}Z`;
25244
+ const d = new Date(iso);
25245
+ if (isNaN(d.getTime()))
25246
+ throw new Error(`Invalid date: ${input}`);
25247
+ return d.toISOString();
25248
+ }
25249
+ throw new Error(`Unrecognized date format: ${input}. Use YYYY-MM-DD or DD.MM.YYYY`);
25250
+ }
25251
+ async function fetchReports2(params) {
25252
+ const { apiKey, apiBaseUrl, projectId, status, limit = 20, beforeDate, beforeId, debug } = params;
25253
+ if (!apiKey) {
25254
+ throw new Error("API key is required");
25255
+ }
25256
+ const base = normalizeBaseUrl(apiBaseUrl);
25257
+ const url = new URL(`${base}/checkup_reports`);
25258
+ url.searchParams.set("order", "id.desc");
25259
+ url.searchParams.set("limit", String(limit));
25260
+ if (typeof projectId === "number") {
25261
+ url.searchParams.set("project_id", `eq.${projectId}`);
25262
+ }
25263
+ if (status) {
25264
+ url.searchParams.set("status", `eq.${status}`);
25265
+ }
25266
+ if (beforeDate) {
25267
+ url.searchParams.set("created_at", `lt.${beforeDate}`);
25268
+ }
25269
+ if (typeof beforeId === "number") {
25270
+ url.searchParams.set("id", `lt.${beforeId}`);
25271
+ }
25272
+ const headers = {
25273
+ "access-token": apiKey,
25274
+ Prefer: "return=representation",
25275
+ "Content-Type": "application/json",
25276
+ Connection: "close"
25277
+ };
25278
+ if (debug) {
25279
+ const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
25280
+ console.error(`Debug: Resolved API base URL: ${base}`);
25281
+ console.error(`Debug: GET URL: ${url.toString()}`);
25282
+ console.error(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
25283
+ }
25284
+ const response = await fetch(url.toString(), { method: "GET", headers });
25285
+ if (debug) {
25286
+ console.error(`Debug: Response status: ${response.status}`);
25287
+ }
25288
+ const data = await response.text();
25289
+ if (response.ok) {
25290
+ try {
25291
+ return JSON.parse(data);
25292
+ } catch {
25293
+ throw new Error(`Failed to parse reports response: ${data}`);
25294
+ }
25295
+ } else {
25296
+ throw new Error(formatHttpError("Failed to fetch reports", response.status, data));
25297
+ }
25298
+ }
25299
+ var MAX_ALL_REPORTS2 = 1e4;
25300
+ async function fetchAllReports2(params) {
25301
+ const pageSize = params.limit ?? 100;
25302
+ const all = [];
25303
+ let beforeId;
25304
+ while (true) {
25305
+ const page = await fetchReports2({ ...params, limit: pageSize, beforeId });
25306
+ if (page.length === 0)
25307
+ break;
25308
+ all.push(...page);
25309
+ if (all.length >= MAX_ALL_REPORTS2) {
25310
+ console.warn(`Warning: reached maximum of ${MAX_ALL_REPORTS2} reports, stopping pagination`);
25311
+ break;
25312
+ }
25313
+ beforeId = page[page.length - 1].id;
25314
+ if (page.length < pageSize)
25315
+ break;
25316
+ }
25317
+ return all;
25318
+ }
25319
+ async function fetchReportFiles2(params) {
25320
+ const { apiKey, apiBaseUrl, reportId, type: type2, checkId, debug } = params;
25321
+ if (!apiKey) {
25322
+ throw new Error("API key is required");
25323
+ }
25324
+ if (reportId === undefined && !checkId) {
25325
+ throw new Error("Either reportId or checkId is required");
25326
+ }
25327
+ const base = normalizeBaseUrl(apiBaseUrl);
25328
+ const url = new URL(`${base}/checkup_report_files`);
25329
+ if (typeof reportId === "number") {
25330
+ url.searchParams.set("checkup_report_id", `eq.${reportId}`);
25331
+ }
25332
+ url.searchParams.set("order", "id.asc");
25333
+ if (type2) {
25334
+ url.searchParams.set("type", `eq.${type2}`);
25335
+ }
25336
+ if (checkId) {
25337
+ url.searchParams.set("check_id", `eq.${checkId}`);
25338
+ }
25339
+ const headers = {
25340
+ "access-token": apiKey,
25341
+ Prefer: "return=representation",
25342
+ "Content-Type": "application/json",
25343
+ Connection: "close"
25344
+ };
25345
+ if (debug) {
25346
+ const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
25347
+ console.error(`Debug: Resolved API base URL: ${base}`);
25348
+ console.error(`Debug: GET URL: ${url.toString()}`);
25349
+ console.error(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
25350
+ }
25351
+ const response = await fetch(url.toString(), { method: "GET", headers });
25352
+ if (debug) {
25353
+ console.error(`Debug: Response status: ${response.status}`);
25354
+ }
25355
+ const data = await response.text();
25356
+ if (response.ok) {
25357
+ try {
25358
+ return JSON.parse(data);
25359
+ } catch {
25360
+ throw new Error(`Failed to parse report files response: ${data}`);
25361
+ }
25362
+ } else {
25363
+ throw new Error(formatHttpError("Failed to fetch report files", response.status, data));
25364
+ }
25365
+ }
25366
+ async function fetchReportFileData2(params) {
25367
+ const { apiKey, apiBaseUrl, reportId, type: type2, checkId, debug } = params;
25368
+ if (!apiKey) {
25369
+ throw new Error("API key is required");
25370
+ }
25371
+ if (reportId === undefined && !checkId) {
25372
+ throw new Error("Either reportId or checkId is required");
25373
+ }
25374
+ const base = normalizeBaseUrl(apiBaseUrl);
25375
+ const url = new URL(`${base}/checkup_report_file_data`);
25376
+ if (typeof reportId === "number") {
25377
+ url.searchParams.set("checkup_report_id", `eq.${reportId}`);
25378
+ }
25379
+ url.searchParams.set("order", "id.asc");
25380
+ if (type2) {
25381
+ url.searchParams.set("type", `eq.${type2}`);
25382
+ }
25383
+ if (checkId) {
25384
+ url.searchParams.set("check_id", `eq.${checkId}`);
25385
+ }
25386
+ const headers = {
25387
+ "access-token": apiKey,
25388
+ Prefer: "return=representation",
25389
+ "Content-Type": "application/json",
25390
+ Connection: "close"
25391
+ };
25392
+ if (debug) {
25393
+ const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
25394
+ console.error(`Debug: Resolved API base URL: ${base}`);
25395
+ console.error(`Debug: GET URL: ${url.toString()}`);
25396
+ console.error(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
25397
+ }
25398
+ const response = await fetch(url.toString(), { method: "GET", headers });
25399
+ if (debug) {
25400
+ console.error(`Debug: Response status: ${response.status}`);
25401
+ }
25402
+ const data = await response.text();
25403
+ if (response.ok) {
25404
+ try {
25405
+ return JSON.parse(data);
25406
+ } catch {
25407
+ throw new Error(`Failed to parse report file data response: ${data}`);
25408
+ }
25409
+ } else {
25410
+ throw new Error(formatHttpError("Failed to fetch report file data", response.status, data));
25411
+ }
25412
+ }
25413
+ function renderMarkdownForTerminal(md) {
25414
+ if (!md)
24594
25415
  return "";
24595
- if (secret.length <= 8)
24596
- return "****";
24597
- if (secret.length <= 16)
24598
- return `${secret.slice(0, 4)}${"*".repeat(secret.length - 8)}${secret.slice(-4)}`;
24599
- return `${secret.slice(0, Math.min(12, secret.length - 8))}${"*".repeat(Math.max(4, secret.length - 16))}${secret.slice(-4)}`;
25416
+ const RESET = "\x1B[0m";
25417
+ const BOLD = "\x1B[1m";
25418
+ const BOLD_UNDERLINE = "\x1B[1;4m";
25419
+ const DIM = "\x1B[2m";
25420
+ const ITALIC = "\x1B[3m";
25421
+ const CYAN = "\x1B[36m";
25422
+ const lines = md.split(`
25423
+ `);
25424
+ const output = [];
25425
+ let inCodeBlock = false;
25426
+ for (const line of lines) {
25427
+ if (line.trimStart().startsWith("```")) {
25428
+ inCodeBlock = !inCodeBlock;
25429
+ if (inCodeBlock) {
25430
+ output.push(`${DIM}${"─".repeat(40)}${RESET}`);
25431
+ } else {
25432
+ output.push(`${DIM}${"─".repeat(40)}${RESET}`);
25433
+ }
25434
+ continue;
25435
+ }
25436
+ if (inCodeBlock) {
25437
+ output.push(`${DIM} ${line}${RESET}`);
25438
+ continue;
25439
+ }
25440
+ if (/^-{3,}$/.test(line.trim()) || /^\*{3,}$/.test(line.trim()) || /^_{3,}$/.test(line.trim())) {
25441
+ output.push(`${DIM}${"─".repeat(60)}${RESET}`);
25442
+ continue;
25443
+ }
25444
+ const headingMatch = line.match(/^(#{1,6})\s+(.*)/);
25445
+ if (headingMatch) {
25446
+ const level = headingMatch[1].length;
25447
+ const text = headingMatch[2];
25448
+ if (level === 1) {
25449
+ output.push(`${BOLD_UNDERLINE}${text}${RESET}`);
25450
+ } else {
25451
+ output.push(`${BOLD}${text}${RESET}`);
25452
+ }
25453
+ continue;
25454
+ }
25455
+ let formatted = line;
25456
+ formatted = formatted.replace(/\*\*(.+?)\*\*/g, `${BOLD}$1${RESET}`);
25457
+ formatted = formatted.replace(/__(.+?)__/g, `${BOLD}$1${RESET}`);
25458
+ formatted = formatted.replace(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, `${ITALIC}$1${RESET}`);
25459
+ formatted = formatted.replace(/(?<=^|[\s(])_([^\s_](?:.*?[^\s_])?)_(?=$|[\s),.:;!?])/g, `${ITALIC}$1${RESET}`);
25460
+ formatted = formatted.replace(/`([^`]+)`/g, `${CYAN}$1${RESET}`);
25461
+ output.push(formatted);
25462
+ }
25463
+ return output.join(`
25464
+ `);
24600
25465
  }
24601
- function normalizeBaseUrl2(value) {
24602
- const trimmed = (value || "").replace(/\/$/, "");
24603
- try {
24604
- new URL(trimmed);
24605
- } catch {
24606
- throw new Error(`Invalid base URL: ${value}`);
25466
+
25467
+ // bin/postgres-ai.ts
25468
+ init_util();
25469
+
25470
+ // lib/storage.ts
25471
+ import * as fs3 from "fs";
25472
+ import * as path3 from "path";
25473
+ var MAX_UPLOAD_SIZE = 500 * 1024 * 1024;
25474
+ var MAX_DOWNLOAD_SIZE = 500 * 1024 * 1024;
25475
+ var MIME_TYPES = {
25476
+ ".png": "image/png",
25477
+ ".jpg": "image/jpeg",
25478
+ ".jpeg": "image/jpeg",
25479
+ ".gif": "image/gif",
25480
+ ".webp": "image/webp",
25481
+ ".svg": "image/svg+xml",
25482
+ ".bmp": "image/bmp",
25483
+ ".ico": "image/x-icon",
25484
+ ".pdf": "application/pdf",
25485
+ ".json": "application/json",
25486
+ ".xml": "application/xml",
25487
+ ".zip": "application/zip",
25488
+ ".gz": "application/gzip",
25489
+ ".tar": "application/x-tar",
25490
+ ".csv": "text/csv",
25491
+ ".txt": "text/plain",
25492
+ ".log": "text/plain",
25493
+ ".sql": "application/sql",
25494
+ ".html": "text/html",
25495
+ ".css": "text/css",
25496
+ ".js": "application/javascript",
25497
+ ".ts": "application/typescript",
25498
+ ".md": "text/markdown",
25499
+ ".yaml": "application/x-yaml",
25500
+ ".yml": "application/x-yaml"
25501
+ };
25502
+ async function uploadFile(params) {
25503
+ const { apiKey, storageBaseUrl, filePath, debug } = params;
25504
+ if (!apiKey) {
25505
+ throw new Error("API key is required");
25506
+ }
25507
+ if (!storageBaseUrl) {
25508
+ throw new Error("storageBaseUrl is required");
25509
+ }
25510
+ if (!filePath) {
25511
+ throw new Error("filePath is required");
25512
+ }
25513
+ const resolvedPath = path3.resolve(filePath);
25514
+ if (!fs3.existsSync(resolvedPath)) {
25515
+ throw new Error(`File not found: ${resolvedPath}`);
25516
+ }
25517
+ const stat = fs3.statSync(resolvedPath);
25518
+ if (!stat.isFile()) {
25519
+ throw new Error(`Not a file: ${resolvedPath}`);
25520
+ }
25521
+ if (stat.size > MAX_UPLOAD_SIZE) {
25522
+ throw new Error(`File too large: ${stat.size} bytes (max ${MAX_UPLOAD_SIZE})`);
25523
+ }
25524
+ const base = normalizeBaseUrl(storageBaseUrl);
25525
+ if (new URL(base).protocol === "http:") {
25526
+ console.error("Warning: storage URL uses HTTP — API key will be sent unencrypted");
25527
+ }
25528
+ const url = `${base}/upload`;
25529
+ const fileBuffer = fs3.readFileSync(resolvedPath);
25530
+ const fileName = path3.basename(resolvedPath);
25531
+ const ext = path3.extname(fileName).toLowerCase();
25532
+ const mimeType = MIME_TYPES[ext] || "application/octet-stream";
25533
+ const formData = new FormData;
25534
+ formData.append("file", new Blob([fileBuffer], { type: mimeType }), fileName);
25535
+ const headers = {
25536
+ "access-token": apiKey
25537
+ };
25538
+ if (debug) {
25539
+ const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
25540
+ console.error(`Debug: Storage base URL: ${base}`);
25541
+ console.error(`Debug: POST URL: ${url}`);
25542
+ console.error(`Debug: File: ${resolvedPath} (${stat.size} bytes, ${mimeType})`);
25543
+ console.error(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
25544
+ }
25545
+ const response = await fetch(url, {
25546
+ method: "POST",
25547
+ headers,
25548
+ body: formData
25549
+ });
25550
+ if (debug) {
25551
+ console.error(`Debug: Response status: ${response.status}`);
25552
+ console.error(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
25553
+ }
25554
+ const data = await response.text();
25555
+ if (response.ok) {
25556
+ try {
25557
+ return JSON.parse(data);
25558
+ } catch {
25559
+ throw new Error(`Failed to parse upload response: ${data}`);
25560
+ }
25561
+ } else {
25562
+ throw new Error(formatHttpError("Failed to upload file", response.status, data));
24607
25563
  }
24608
- return trimmed;
24609
25564
  }
24610
- function resolveBaseUrls2(opts, cfg, defaults2 = {}) {
24611
- const defApi = defaults2.apiBaseUrl || "https://postgres.ai/api/general/";
24612
- const defUi = defaults2.uiBaseUrl || "https://console.postgres.ai";
24613
- const apiCandidate = opts?.apiBaseUrl || process.env.PGAI_API_BASE_URL || cfg?.baseUrl || defApi;
24614
- const uiCandidate = opts?.uiBaseUrl || process.env.PGAI_UI_BASE_URL || defUi;
25565
+ async function downloadFile(params) {
25566
+ const { apiKey, storageBaseUrl, fileUrl, outputPath, debug } = params;
25567
+ if (!apiKey) {
25568
+ throw new Error("API key is required");
25569
+ }
25570
+ if (!storageBaseUrl) {
25571
+ throw new Error("storageBaseUrl is required");
25572
+ }
25573
+ if (!fileUrl) {
25574
+ throw new Error("fileUrl is required");
25575
+ }
25576
+ const base = normalizeBaseUrl(storageBaseUrl);
25577
+ if (new URL(base).protocol === "http:") {
25578
+ console.error("Warning: storage URL uses HTTP — API key will be sent unencrypted");
25579
+ }
25580
+ let fullUrl;
25581
+ if (fileUrl.startsWith("http://") || fileUrl.startsWith("https://")) {
25582
+ if (!fileUrl.startsWith(base + "/")) {
25583
+ throw new Error(`URL must be under storage base URL: ${base}`);
25584
+ }
25585
+ fullUrl = fileUrl;
25586
+ } else {
25587
+ const relativePath = fileUrl.startsWith("/") ? fileUrl : `/${fileUrl}`;
25588
+ fullUrl = `${base}${relativePath}`;
25589
+ }
25590
+ const urlFilename = path3.basename(new URL(fullUrl).pathname);
25591
+ if (!urlFilename) {
25592
+ throw new Error("Cannot derive filename from URL; please specify --output");
25593
+ }
25594
+ const saveTo = outputPath ? path3.resolve(outputPath) : path3.resolve(urlFilename);
25595
+ if (!outputPath) {
25596
+ const normalizedSave = path3.normalize(saveTo);
25597
+ if (!normalizedSave.startsWith(path3.normalize(process.cwd()))) {
25598
+ throw new Error("Derived output path escapes current directory; please specify --output");
25599
+ }
25600
+ }
25601
+ const headers = {
25602
+ "access-token": apiKey
25603
+ };
25604
+ if (debug) {
25605
+ const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
25606
+ console.error(`Debug: Storage base URL: ${base}`);
25607
+ console.error(`Debug: GET URL: ${fullUrl}`);
25608
+ console.error(`Debug: Output: ${saveTo}`);
25609
+ console.error(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
25610
+ }
25611
+ const response = await fetch(fullUrl, {
25612
+ method: "GET",
25613
+ headers
25614
+ });
25615
+ if (debug) {
25616
+ console.error(`Debug: Response status: ${response.status}`);
25617
+ console.error(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
25618
+ }
25619
+ if (!response.ok) {
25620
+ const data = await response.text();
25621
+ throw new Error(formatHttpError("Failed to download file", response.status, data));
25622
+ }
25623
+ const contentLength = response.headers.get("content-length");
25624
+ if (contentLength && parseInt(contentLength, 10) > MAX_DOWNLOAD_SIZE) {
25625
+ throw new Error(`File too large: ${contentLength} bytes (max ${MAX_DOWNLOAD_SIZE})`);
25626
+ }
25627
+ const arrayBuffer = await response.arrayBuffer();
25628
+ const buffer = Buffer.from(arrayBuffer);
25629
+ const parentDir = path3.dirname(saveTo);
25630
+ if (!fs3.existsSync(parentDir)) {
25631
+ fs3.mkdirSync(parentDir, { recursive: true });
25632
+ }
25633
+ fs3.writeFileSync(saveTo, buffer);
24615
25634
  return {
24616
- apiBaseUrl: normalizeBaseUrl2(apiCandidate),
24617
- uiBaseUrl: normalizeBaseUrl2(uiCandidate)
25635
+ savedTo: saveTo,
25636
+ size: buffer.length,
25637
+ mimeType: response.headers.get("content-type")
24618
25638
  };
24619
25639
  }
25640
+ var IMAGE_EXTENSIONS = new Set([".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg", ".bmp", ".ico"]);
25641
+ function buildMarkdownLink(fileUrl, storageBaseUrl, filename) {
25642
+ const base = normalizeBaseUrl(storageBaseUrl);
25643
+ const normalizedFileUrl = fileUrl.startsWith("/") ? fileUrl : `/${fileUrl}`;
25644
+ const fullUrl = fileUrl.startsWith("http://") || fileUrl.startsWith("https://") ? fileUrl : `${base}${normalizedFileUrl}`;
25645
+ const name = filename || path3.basename(new URL(fullUrl).pathname);
25646
+ const safeName = name.replace(/[\[\]()]/g, "\\$&");
25647
+ const ext = path3.extname(name).toLowerCase();
25648
+ if (IMAGE_EXTENSIONS.has(ext)) {
25649
+ return `![${safeName}](${fullUrl})`;
25650
+ }
25651
+ return `[${safeName}](${fullUrl})`;
25652
+ }
24620
25653
 
24621
25654
  // lib/init.ts
24622
25655
  import { randomBytes } from "crypto";
24623
25656
  import { URL as URL2, fileURLToPath } from "url";
24624
- import * as fs3 from "fs";
24625
- import * as path3 from "path";
25657
+ import * as fs4 from "fs";
25658
+ import * as path4 from "path";
24626
25659
  var DEFAULT_MONITORING_USER = "postgres_ai_mon";
24627
25660
  var KNOWN_PROVIDERS = ["self-managed", "supabase"];
24628
25661
  var SKIP_ROLE_CREATION_PROVIDERS = ["supabase"];
@@ -24678,8 +25711,9 @@ function isSslNegotiationError(err) {
24678
25711
  }
24679
25712
  async function connectWithSslFallback(ClientClass, adminConn, verbose) {
24680
25713
  const tryConnect = async (config2) => {
24681
- const client = new ClientClass(config2);
25714
+ const client = new ClientClass({ ...config2, connectionTimeoutMillis: 1e4 });
24682
25715
  await client.connect();
25716
+ await client.query("SET statement_timeout = '30s'");
24683
25717
  return client;
24684
25718
  };
24685
25719
  if (!adminConn.sslFallbackEnabled) {
@@ -24694,7 +25728,7 @@ async function connectWithSslFallback(ClientClass, adminConn, verbose) {
24694
25728
  throw sslErr;
24695
25729
  }
24696
25730
  if (verbose) {
24697
- console.log("SSL connection failed, retrying without SSL...");
25731
+ console.error("SSL connection failed, retrying without SSL...");
24698
25732
  }
24699
25733
  const noSslConfig = { ...adminConn.clientConfig, ssl: false };
24700
25734
  try {
@@ -24713,21 +25747,21 @@ async function connectWithSslFallback(ClientClass, adminConn, verbose) {
24713
25747
  }
24714
25748
  function sqlDir() {
24715
25749
  const currentFile = fileURLToPath(import.meta.url);
24716
- const currentDir = path3.dirname(currentFile);
25750
+ const currentDir = path4.dirname(currentFile);
24717
25751
  const candidates = [
24718
- path3.resolve(currentDir, "..", "sql"),
24719
- path3.resolve(currentDir, "..", "..", "sql")
25752
+ path4.resolve(currentDir, "..", "sql"),
25753
+ path4.resolve(currentDir, "..", "..", "sql")
24720
25754
  ];
24721
25755
  for (const candidate of candidates) {
24722
- if (fs3.existsSync(candidate)) {
25756
+ if (fs4.existsSync(candidate)) {
24723
25757
  return candidate;
24724
25758
  }
24725
25759
  }
24726
25760
  throw new Error(`SQL directory not found. Searched: ${candidates.join(", ")}`);
24727
25761
  }
24728
25762
  function loadSqlTemplate(filename) {
24729
- const p = path3.join(sqlDir(), filename);
24730
- return fs3.readFileSync(p, "utf8");
25763
+ const p = path4.join(sqlDir(), filename);
25764
+ return fs4.readFileSync(p, "utf8");
24731
25765
  }
24732
25766
  function applyTemplate(sql, vars) {
24733
25767
  return sql.replace(/\{\{([A-Z0-9_]+)\}\}/g, (_, key) => {
@@ -25148,7 +26182,12 @@ async function verifyInitSetup(params) {
25148
26182
  if (!schemaExistsRes.rows?.[0]?.ok) {
25149
26183
  missingRequired.push("USAGE on schema postgres_ai");
25150
26184
  }
25151
- const viewExistsRes = await params.client.query("select to_regclass('postgres_ai.pg_statistic') is not null as ok");
26185
+ const viewExistsRes = await params.client.query(`
26186
+ select case
26187
+ when not has_schema_privilege(current_user, 'postgres_ai', 'USAGE') then null
26188
+ else to_regclass('postgres_ai.pg_statistic') is not null
26189
+ end as ok
26190
+ `);
25152
26191
  if (!viewExistsRes.rows?.[0]?.ok) {
25153
26192
  missingRequired.push("view postgres_ai.pg_statistic exists");
25154
26193
  } else {
@@ -25232,6 +26271,119 @@ async function verifyInitSetup(params) {
25232
26271
  } catch {}
25233
26272
  }
25234
26273
  }
26274
+ async function checkCurrentUserPermissions(client) {
26275
+ const sql = `
26276
+ with permission_checks as (
26277
+ select
26278
+ format('connect on database %I', current_database()) as permission_name,
26279
+ 'required' as status,
26280
+ has_database_privilege(current_user, current_database(), 'connect') as granted
26281
+
26282
+ union all
26283
+
26284
+ select
26285
+ 'pg_monitor role membership' as permission_name,
26286
+ 'required' as status,
26287
+ -- CASE guarantees evaluation order: pg_has_role() is only called if the
26288
+ -- pg_monitor role exists, avoiding ERROR on PostgreSQL < 10 or when dropped.
26289
+ case
26290
+ when not exists (select from pg_roles where rolname = 'pg_monitor')
26291
+ then false
26292
+ else pg_has_role(current_user, 'pg_monitor', 'member')
26293
+ end as granted
26294
+
26295
+ union all
26296
+
26297
+ select
26298
+ 'select on pg_catalog.pg_index' as permission_name,
26299
+ 'required' as status,
26300
+ has_table_privilege(current_user, 'pg_catalog.pg_index', 'select') as granted
26301
+
26302
+ union all
26303
+
26304
+ select
26305
+ 'postgres_ai.pg_statistic view exists' as permission_name,
26306
+ 'optional' as status,
26307
+ case
26308
+ when not has_schema_privilege(current_user, 'postgres_ai', 'USAGE') then null
26309
+ else to_regclass('postgres_ai.pg_statistic') is not null
26310
+ end as granted
26311
+
26312
+ union all
26313
+
26314
+ select
26315
+ 'select on postgres_ai.pg_statistic' as permission_name,
26316
+ 'optional' as status,
26317
+ case
26318
+ when not has_schema_privilege(current_user, 'postgres_ai', 'USAGE') then null
26319
+ when to_regclass('postgres_ai.pg_statistic') is null then null
26320
+ else has_table_privilege(current_user, 'postgres_ai.pg_statistic', 'select')
26321
+ end as granted
26322
+ )
26323
+ select
26324
+ permission_name,
26325
+ status,
26326
+ granted,
26327
+ case
26328
+ when status = 'required' and not coalesce(granted, false) then
26329
+ case
26330
+ when permission_name like 'connect%' then
26331
+ format('grant connect on database %I to %I;', current_database(), current_user)
26332
+ when permission_name = 'pg_monitor role membership' then
26333
+ format('grant pg_monitor to %I;', current_user)
26334
+ when permission_name like 'select on pg_catalog.pg_index' then
26335
+ format('grant select on pg_catalog.pg_index to %I;', current_user)
26336
+ end
26337
+ when permission_name = 'postgres_ai.pg_statistic view exists' and granted = false then
26338
+ '-- create postgres_ai.pg_statistic view (see setup script)'
26339
+ when permission_name = 'select on postgres_ai.pg_statistic' and granted = false then
26340
+ format('grant select on postgres_ai.pg_statistic to %I;', current_user)
26341
+ else null
26342
+ end as fix_command
26343
+ from permission_checks
26344
+ order by
26345
+ case status when 'required' then 1 else 2 end,
26346
+ permission_name;
26347
+ `;
26348
+ const res = await client.query(sql);
26349
+ const rows = res.rows;
26350
+ const missingRequired = rows.filter((r) => r.status === "required" && r.granted !== true);
26351
+ const missingOptional = rows.filter((r) => r.status === "optional" && r.granted === false);
26352
+ return {
26353
+ ok: missingRequired.length === 0,
26354
+ rows,
26355
+ missingRequired,
26356
+ missingOptional
26357
+ };
26358
+ }
26359
+ function formatPermissionCheckMessages(result) {
26360
+ const warnings = [];
26361
+ const errors3 = [];
26362
+ for (const row of result.missingOptional) {
26363
+ const fix = row.fix_command ? ` Fix: ${row.fix_command}` : "";
26364
+ warnings.push(`Warning: optional permission missing — ${row.permission_name}.${fix}`);
26365
+ }
26366
+ if (!result.ok) {
26367
+ errors3.push(`Error: the database user is missing required permissions.
26368
+ `);
26369
+ errors3.push("Missing permissions:");
26370
+ for (const row of result.missingRequired) {
26371
+ errors3.push(` - ${row.permission_name}`);
26372
+ }
26373
+ const fixes = result.missingRequired.map((r) => r.fix_command).filter(Boolean);
26374
+ if (fixes.length > 0) {
26375
+ errors3.push(`
26376
+ To fix, run the following as a superuser:
26377
+ `);
26378
+ for (const fix of fixes) {
26379
+ errors3.push(` ${fix}`);
26380
+ }
26381
+ }
26382
+ errors3.push(`
26383
+ Alternatively, run 'postgresai prepare-db' to set up permissions automatically.`);
26384
+ }
26385
+ return { failed: !result.ok, warnings, errors: errors3 };
26386
+ }
25235
26387
 
25236
26388
  // lib/supabase.ts
25237
26389
  var SUPABASE_API_BASE = "https://api.supabase.com";
@@ -25394,6 +26546,9 @@ class SupabaseClient {
25394
26546
  }
25395
26547
  }
25396
26548
  async function fetchPoolerDatabaseUrl(config2, username) {
26549
+ if (!isValidProjectRef(config2.projectRef)) {
26550
+ throw new Error(`Invalid Supabase project reference format: "${config2.projectRef}". Expected 10-30 alphanumeric characters.`);
26551
+ }
25397
26552
  const url = `${SUPABASE_API_BASE}/v1/projects/${encodeURIComponent(config2.projectRef)}/config/database/pooler`;
25398
26553
  const suffix = `.${config2.projectRef}`;
25399
26554
  const effectiveUsername = username.endsWith(suffix) ? username : `${username}${suffix}`;
@@ -25566,7 +26721,10 @@ async function verifyInitSetupViaSupabase(params) {
25566
26721
  missingRequired.push("USAGE on schema postgres_ai");
25567
26722
  }
25568
26723
  }
25569
- const viewExistsRes = await params.client.query("SELECT to_regclass('postgres_ai.pg_statistic') IS NOT NULL as ok", true);
26724
+ const viewExistsRes = await params.client.query(`SELECT CASE
26725
+ WHEN NOT has_schema_privilege(current_user, 'postgres_ai', 'USAGE') THEN NULL
26726
+ ELSE to_regclass('postgres_ai.pg_statistic') IS NOT NULL
26727
+ END as ok`, true);
25570
26728
  if (!viewExistsRes.rows?.[0]?.ok) {
25571
26729
  missingRequired.push("view postgres_ai.pg_statistic exists");
25572
26730
  } else {
@@ -25706,12 +26864,12 @@ function createCallbackServer(port = 0, expectedState = null, timeoutMs = 300000
25706
26864
  let resolveReady;
25707
26865
  let rejectReady;
25708
26866
  let serverInstance = null;
25709
- const promise2 = new Promise((resolve4, reject) => {
25710
- resolveCallback = resolve4;
26867
+ const promise2 = new Promise((resolve5, reject) => {
26868
+ resolveCallback = resolve5;
25711
26869
  rejectCallback = reject;
25712
26870
  });
25713
- const ready = new Promise((resolve4, reject) => {
25714
- resolveReady = resolve4;
26871
+ const ready = new Promise((resolve5, reject) => {
26872
+ resolveReady = resolve5;
25715
26873
  rejectReady = reject;
25716
26874
  });
25717
26875
  let timeoutId = null;
@@ -25897,12 +27055,13 @@ function createCallbackServer(port = 0, expectedState = null, timeoutMs = 300000
25897
27055
  }
25898
27056
 
25899
27057
  // bin/postgres-ai.ts
27058
+ init_util();
25900
27059
  import { createInterface } from "readline";
25901
27060
  import * as childProcess from "child_process";
25902
27061
 
25903
27062
  // lib/checkup.ts
25904
- import * as fs4 from "fs";
25905
- import * as path4 from "path";
27063
+ import * as fs5 from "fs";
27064
+ import * as path5 from "path";
25906
27065
 
25907
27066
  // lib/metrics-embedded.ts
25908
27067
  var METRICS = {
@@ -26710,6 +27869,65 @@ limit 1000
26710
27869
  },
26711
27870
  gauges: ["real_size_mib", "table_size_mib", "extra_size", "extra_pct", "fillfactor", "bloat_size", "bloat_pct", "is_na", "reltuples"],
26712
27871
  statement_timeout_seconds: 15
27872
+ },
27873
+ pg_stat_io: {
27874
+ description: "Collects I/O statistics from the PostgreSQL `pg_stat_io` view (available in PostgreSQL 16+). Provides insights into read and write operations by backend type, including the number of operations, MiB transferred (divided by 1048576), and time spent on I/O. This metric is essential for monitoring I/O performance and identifying potential bottlenecks in disk operations. The data is aggregated by backend type with a total row included via ROLLUP.",
27875
+ sqls: {
27876
+ 11: "; -- pg_stat_io only available in PostgreSQL 16+",
27877
+ 16: `select /* pgwatch_generated */
27878
+ (extract(epoch from now()) * 1e9)::int8 as epoch_ns,
27879
+ current_database() as tag_datname,
27880
+ coalesce(backend_type, 'total') as tag_backend_type,
27881
+ sum(coalesce(reads, 0))::int8 as reads,
27882
+ (sum(coalesce(reads, 0) * op_bytes) / 1048576.0)::int8 as read_bytes_mb,
27883
+ sum(coalesce(read_time, 0))::int8 as read_time_ms,
27884
+ sum(coalesce(writes, 0))::int8 as writes,
27885
+ (sum(coalesce(writes, 0) * op_bytes) / 1048576.0)::int8 as write_bytes_mb,
27886
+ sum(coalesce(write_time, 0))::int8 as write_time_ms,
27887
+ sum(coalesce(writebacks, 0))::int8 as writebacks,
27888
+ (sum(coalesce(writebacks, 0) * op_bytes) / 1048576.0)::int8 as writeback_bytes_mb,
27889
+ sum(coalesce(writeback_time, 0))::int8 as writeback_time_ms,
27890
+ sum(coalesce(fsyncs, 0))::int8 as fsyncs,
27891
+ sum(coalesce(fsync_time, 0))::int8 as fsync_time_ms,
27892
+ sum(coalesce(extends, 0))::int8 as extends,
27893
+ (sum(coalesce(extends, 0) * op_bytes) / 1048576.0)::int8 as extend_bytes_mb,
27894
+ sum(coalesce(hits, 0))::int8 as hits,
27895
+ sum(coalesce(evictions, 0))::int8 as evictions,
27896
+ sum(coalesce(reuses, 0))::int8 as reuses,
27897
+ max(extract(epoch from now() - stats_reset)::int) as stats_reset_s
27898
+ from
27899
+ pg_stat_io
27900
+ group by
27901
+ rollup (backend_type)`,
27902
+ 18: `select /* pgwatch_generated */
27903
+ (extract(epoch from now()) * 1e9)::int8 as epoch_ns,
27904
+ current_database() as tag_datname,
27905
+ coalesce(backend_type, 'total') as tag_backend_type,
27906
+ sum(coalesce(reads, 0))::int8 as reads,
27907
+ (sum(coalesce(read_bytes, 0)) / 1048576.0)::int8 as read_bytes_mb,
27908
+ sum(coalesce(read_time, 0))::int8 as read_time_ms,
27909
+ sum(coalesce(writes, 0))::int8 as writes,
27910
+ (sum(coalesce(write_bytes, 0)) / 1048576.0)::int8 as write_bytes_mb,
27911
+ sum(coalesce(write_time, 0))::int8 as write_time_ms,
27912
+ sum(coalesce(writebacks, 0))::int8 as writebacks,
27913
+ -- PostgreSQL 18 has no writeback_bytes column; rows with NULL op_bytes contribute zero by design.
27914
+ (sum(coalesce(writebacks, 0) * coalesce(op_bytes, 0)) / 1048576.0)::int8 as writeback_bytes_mb,
27915
+ sum(coalesce(writeback_time, 0))::int8 as writeback_time_ms,
27916
+ sum(coalesce(fsyncs, 0))::int8 as fsyncs,
27917
+ sum(coalesce(fsync_time, 0))::int8 as fsync_time_ms,
27918
+ sum(coalesce(extends, 0))::int8 as extends,
27919
+ (sum(coalesce(extend_bytes, 0)) / 1048576.0)::int8 as extend_bytes_mb,
27920
+ sum(coalesce(hits, 0))::int8 as hits,
27921
+ sum(coalesce(evictions, 0))::int8 as evictions,
27922
+ sum(coalesce(reuses, 0))::int8 as reuses,
27923
+ max(extract(epoch from now() - stats_reset)::int) as stats_reset_s
27924
+ from
27925
+ pg_stat_io
27926
+ group by
27927
+ rollup (backend_type)`
27928
+ },
27929
+ gauges: ["*"],
27930
+ statement_timeout_seconds: 15
26713
27931
  }
26714
27932
  };
26715
27933
 
@@ -26735,7 +27953,8 @@ var METRIC_NAMES = {
26735
27953
  settings: "settings",
26736
27954
  dbStats: "db_stats",
26737
27955
  dbSize: "db_size",
26738
- statsReset: "stats_reset"
27956
+ statsReset: "stats_reset",
27957
+ I001: "pg_stat_io"
26739
27958
  };
26740
27959
  function transformMetricRow(row) {
26741
27960
  const result = {};
@@ -27335,7 +28554,7 @@ function parseVersionNum(versionNum) {
27335
28554
  };
27336
28555
  } catch (err) {
27337
28556
  const errorMsg = err instanceof Error ? err.message : String(err);
27338
- console.log(`[parseVersionNum] Warning: Failed to parse "${versionNum}": ${errorMsg}`);
28557
+ console.error(`[parseVersionNum] Warning: Failed to parse "${versionNum}": ${errorMsg}`);
27339
28558
  return { major: "", minor: "" };
27340
28559
  }
27341
28560
  }
@@ -27652,7 +28871,7 @@ async function getStatsReset(client, pgMajorVersion = 16) {
27652
28871
  } catch (err) {
27653
28872
  const errorMsg = err instanceof Error ? err.message : String(err);
27654
28873
  postmasterStartupError = `Failed to query postmaster start time: ${errorMsg}`;
27655
- console.log(`[getStatsReset] Warning: ${postmasterStartupError}`);
28874
+ console.error(`[getStatsReset] Warning: ${postmasterStartupError}`);
27656
28875
  }
27657
28876
  const statsResult = {
27658
28877
  stats_reset_epoch: statsResetEpoch,
@@ -27705,7 +28924,7 @@ async function getRedundantIndexes(client, pgMajorVersion = 16) {
27705
28924
  const errorMsg = err instanceof Error ? err.message : String(err);
27706
28925
  const indexName = String(transformed.index_name || "unknown");
27707
28926
  parseError = `Failed to parse redundant_to_json: ${errorMsg}`;
27708
- console.log(`[H004] Warning: ${parseError} for index "${indexName}"`);
28927
+ console.error(`[H004] Warning: ${parseError} for index "${indexName}"`);
27709
28928
  }
27710
28929
  const result2 = {
27711
28930
  schema_name: String(transformed.schema_name || ""),
@@ -27747,7 +28966,7 @@ function createBaseReport(checkId, checkTitle, nodeName) {
27747
28966
  }
27748
28967
  function readTextFileSafe(p) {
27749
28968
  try {
27750
- const value = fs4.readFileSync(p, "utf8").trim();
28969
+ const value = fs5.readFileSync(p, "utf8").trim();
27751
28970
  return value || null;
27752
28971
  } catch {
27753
28972
  return null;
@@ -27760,8 +28979,8 @@ function resolveBuildTs() {
27760
28979
  if (fromFile)
27761
28980
  return fromFile;
27762
28981
  try {
27763
- const pkgRoot = path4.resolve(__dirname, "..");
27764
- const fromPkgFile = readTextFileSafe(path4.join(pkgRoot, "BUILD_TS"));
28982
+ const pkgRoot = path5.resolve(__dirname, "..");
28983
+ const fromPkgFile = readTextFileSafe(path5.join(pkgRoot, "BUILD_TS"));
27765
28984
  if (fromPkgFile)
27766
28985
  return fromPkgFile;
27767
28986
  } catch (err) {
@@ -27769,13 +28988,13 @@ function resolveBuildTs() {
27769
28988
  console.warn(`[resolveBuildTs] Warning: path resolution failed: ${errorMsg}`);
27770
28989
  }
27771
28990
  try {
27772
- const pkgJsonPath = path4.resolve(__dirname, "..", "package.json");
27773
- const st = fs4.statSync(pkgJsonPath);
28991
+ const pkgJsonPath = path5.resolve(__dirname, "..", "package.json");
28992
+ const st = fs5.statSync(pkgJsonPath);
27774
28993
  return st.mtime.toISOString();
27775
28994
  } catch (err) {
27776
28995
  if (process.env.DEBUG) {
27777
28996
  const errorMsg = err instanceof Error ? err.message : String(err);
27778
- console.log(`[resolveBuildTs] Could not stat package.json, using current time: ${errorMsg}`);
28997
+ console.error(`[resolveBuildTs] Could not stat package.json, using current time: ${errorMsg}`);
27779
28998
  }
27780
28999
  return new Date().toISOString();
27781
29000
  }
@@ -27885,7 +29104,7 @@ async function generateD004(client, nodeName) {
27885
29104
  }
27886
29105
  } catch (err) {
27887
29106
  const errorMsg = err instanceof Error ? err.message : String(err);
27888
- console.log(`[D004] Error querying pg_stat_statements: ${errorMsg}`);
29107
+ console.error(`[D004] Error querying pg_stat_statements: ${errorMsg}`);
27889
29108
  pgssError = errorMsg;
27890
29109
  }
27891
29110
  let kcacheAvailable = false;
@@ -27931,7 +29150,7 @@ async function generateD004(client, nodeName) {
27931
29150
  }
27932
29151
  } catch (err) {
27933
29152
  const errorMsg = err instanceof Error ? err.message : String(err);
27934
- console.log(`[D004] Error querying pg_stat_kcache: ${errorMsg}`);
29153
+ console.error(`[D004] Error querying pg_stat_kcache: ${errorMsg}`);
27935
29154
  kcacheError = errorMsg;
27936
29155
  }
27937
29156
  report.results[nodeName] = {
@@ -28042,7 +29261,10 @@ async function generateF004(client, nodeName) {
28042
29261
  });
28043
29262
  } catch (err) {
28044
29263
  const errorMsg = err instanceof Error ? err.message : String(err);
28045
- console.log(`[F004] Error estimating table bloat: ${errorMsg}`);
29264
+ console.error(`[F004] Error estimating table bloat: ${errorMsg}`);
29265
+ if (errorMsg.includes("postgres_ai.")) {
29266
+ console.error(` Hint: Run "postgresai prepare-db <connection>" to create required objects.`);
29267
+ }
28046
29268
  }
28047
29269
  const { datname: dbName, size_bytes: dbSizeBytes } = await getCurrentDatabaseInfo(client, pgMajorVersion);
28048
29270
  const totalCount = bloatedTables.length;
@@ -28116,7 +29338,10 @@ async function generateF005(client, nodeName) {
28116
29338
  });
28117
29339
  } catch (err) {
28118
29340
  const errorMsg = err instanceof Error ? err.message : String(err);
28119
- console.log(`[F005] Error estimating index bloat: ${errorMsg}`);
29341
+ console.error(`[F005] Error estimating index bloat: ${errorMsg}`);
29342
+ if (errorMsg.includes("postgres_ai.")) {
29343
+ console.error(` Hint: Run "postgresai prepare-db <connection>" to create required objects.`);
29344
+ }
28120
29345
  }
28121
29346
  const { datname: dbName, size_bytes: dbSizeBytes } = await getCurrentDatabaseInfo(client, pgMajorVersion);
28122
29347
  const totalCount = bloatedIndexes.length;
@@ -28203,7 +29428,7 @@ async function generateG001(client, nodeName) {
28203
29428
  }
28204
29429
  } catch (err) {
28205
29430
  const errorMsg = err instanceof Error ? err.message : String(err);
28206
- console.log(`[G001] Error calculating memory usage: ${errorMsg}`);
29431
+ console.error(`[G001] Error calculating memory usage: ${errorMsg}`);
28207
29432
  memoryError = errorMsg;
28208
29433
  }
28209
29434
  report.results[nodeName] = {
@@ -28263,14 +29488,148 @@ async function generateG003(client, nodeName) {
28263
29488
  }
28264
29489
  } catch (err) {
28265
29490
  const errorMsg = err instanceof Error ? err.message : String(err);
28266
- console.log(`[G003] Error querying deadlock stats: ${errorMsg}`);
28267
- deadlockError = errorMsg;
29491
+ console.error(`[G003] Error querying deadlock stats: ${errorMsg}`);
29492
+ deadlockError = errorMsg;
29493
+ }
29494
+ report.results[nodeName] = {
29495
+ data: {
29496
+ settings: lockSettings,
29497
+ deadlock_stats: deadlockStats,
29498
+ ...deadlockError && { deadlock_stats_error: deadlockError }
29499
+ },
29500
+ postgres_version: postgresVersion
29501
+ };
29502
+ return report;
29503
+ }
29504
+ async function getIOStatistics(client, pgMajorVersion = 0, metricSqlOverride) {
29505
+ if (pgMajorVersion < 16) {
29506
+ return [];
29507
+ }
29508
+ try {
29509
+ const sql = metricSqlOverride ?? getMetricSql(METRIC_NAMES.I001, pgMajorVersion);
29510
+ if (!sql || sql.trim().startsWith(";")) {
29511
+ return [];
29512
+ }
29513
+ const result = await client.query(sql);
29514
+ return result.rows.map((row) => {
29515
+ const transformed = transformMetricRow(row);
29516
+ return {
29517
+ backend_type: String(transformed.backend_type || "unknown"),
29518
+ reads: parseInt(String(transformed.reads || 0), 10),
29519
+ read_bytes_mb: parseInt(String(transformed.read_bytes_mb || 0), 10),
29520
+ read_time_ms: parseInt(String(transformed.read_time_ms || 0), 10),
29521
+ writes: parseInt(String(transformed.writes || 0), 10),
29522
+ write_bytes_mb: parseInt(String(transformed.write_bytes_mb || 0), 10),
29523
+ write_time_ms: parseInt(String(transformed.write_time_ms || 0), 10),
29524
+ writebacks: parseInt(String(transformed.writebacks || 0), 10),
29525
+ writeback_bytes_mb: parseInt(String(transformed.writeback_bytes_mb || 0), 10),
29526
+ writeback_time_ms: parseInt(String(transformed.writeback_time_ms || 0), 10),
29527
+ fsyncs: parseInt(String(transformed.fsyncs || 0), 10),
29528
+ fsync_time_ms: parseInt(String(transformed.fsync_time_ms || 0), 10),
29529
+ extends: parseInt(String(transformed.extends || 0), 10),
29530
+ extend_bytes_mb: parseInt(String(transformed.extend_bytes_mb || 0), 10),
29531
+ hits: parseInt(String(transformed.hits || 0), 10),
29532
+ evictions: parseInt(String(transformed.evictions || 0), 10),
29533
+ reuses: parseInt(String(transformed.reuses || 0), 10)
29534
+ };
29535
+ });
29536
+ } catch (err) {
29537
+ const errorMsg = err instanceof Error ? err.message : String(err);
29538
+ console.log(`[I001] Error fetching I/O statistics: ${errorMsg}`);
29539
+ return [];
29540
+ }
29541
+ }
29542
+ async function generateI001(client, nodeName) {
29543
+ const report = createBaseReport("I001", "I/O statistics (pg_stat_io)", nodeName);
29544
+ const postgresVersion = await getPostgresVersion(client);
29545
+ const parsedPgMajorVersion = parseInt(postgresVersion.server_major_ver, 10);
29546
+ const pgMajorVersion = Number.isFinite(parsedPgMajorVersion) ? parsedPgMajorVersion : 0;
29547
+ if (pgMajorVersion < 16) {
29548
+ report.results[nodeName] = {
29549
+ data: {
29550
+ available: false,
29551
+ min_version_required: "16",
29552
+ by_backend_type: [],
29553
+ analysis: {
29554
+ total_read_mb: 0,
29555
+ total_write_mb: 0,
29556
+ total_io_time_ms: 0,
29557
+ read_hit_ratio_pct: 0,
29558
+ avg_read_time_ms: null,
29559
+ avg_write_time_ms: null
29560
+ },
29561
+ stats_reset_s: null
29562
+ },
29563
+ postgres_version: postgresVersion
29564
+ };
29565
+ return report;
29566
+ }
29567
+ const ioStats = await getIOStatistics(client, pgMajorVersion);
29568
+ ioStats.sort((a, b) => {
29569
+ if (a.backend_type === "total")
29570
+ return -1;
29571
+ if (b.backend_type === "total")
29572
+ return 1;
29573
+ return a.backend_type.localeCompare(b.backend_type);
29574
+ });
29575
+ let totalStats = ioStats.find((s) => s.backend_type === "total");
29576
+ if (!totalStats && ioStats.length > 0) {
29577
+ totalStats = {
29578
+ backend_type: "total",
29579
+ reads: ioStats.reduce((sum, s) => sum + s.reads, 0),
29580
+ read_bytes_mb: ioStats.reduce((sum, s) => sum + s.read_bytes_mb, 0),
29581
+ read_time_ms: ioStats.reduce((sum, s) => sum + s.read_time_ms, 0),
29582
+ writes: ioStats.reduce((sum, s) => sum + s.writes, 0),
29583
+ write_bytes_mb: ioStats.reduce((sum, s) => sum + s.write_bytes_mb, 0),
29584
+ write_time_ms: ioStats.reduce((sum, s) => sum + s.write_time_ms, 0),
29585
+ writebacks: ioStats.reduce((sum, s) => sum + s.writebacks, 0),
29586
+ writeback_bytes_mb: ioStats.reduce((sum, s) => sum + s.writeback_bytes_mb, 0),
29587
+ writeback_time_ms: ioStats.reduce((sum, s) => sum + s.writeback_time_ms, 0),
29588
+ fsyncs: ioStats.reduce((sum, s) => sum + s.fsyncs, 0),
29589
+ fsync_time_ms: ioStats.reduce((sum, s) => sum + s.fsync_time_ms, 0),
29590
+ extends: ioStats.reduce((sum, s) => sum + (s.extends || 0), 0),
29591
+ extend_bytes_mb: ioStats.reduce((sum, s) => sum + (s.extend_bytes_mb || 0), 0),
29592
+ hits: ioStats.reduce((sum, s) => sum + s.hits, 0),
29593
+ evictions: ioStats.reduce((sum, s) => sum + s.evictions, 0),
29594
+ reuses: ioStats.reduce((sum, s) => sum + s.reuses, 0)
29595
+ };
28268
29596
  }
29597
+ const totalReadMb = totalStats?.read_bytes_mb || 0;
29598
+ const totalWriteMb = totalStats?.write_bytes_mb || 0;
29599
+ const totalReadTime = totalStats?.read_time_ms || 0;
29600
+ const totalWriteTime = totalStats?.write_time_ms || 0;
29601
+ const totalIoTimeMs = totalReadTime + totalWriteTime;
29602
+ const totalReads = totalStats?.reads || 0;
29603
+ const totalWrites = totalStats?.writes || 0;
29604
+ const totalHits = totalStats?.hits || 0;
29605
+ const totalRequests = totalHits + totalReads;
29606
+ const readHitRatioPct = totalRequests > 0 ? Math.round(totalHits / totalRequests * 1e4) / 100 : 0;
29607
+ const avgReadTimeMs = totalReads > 0 ? Math.round(totalReadTime / totalReads * 1000) / 1000 : null;
29608
+ const avgWriteTimeMs = totalWrites > 0 ? Math.round(totalWriteTime / totalWrites * 1000) / 1000 : null;
29609
+ let statsResetS = null;
29610
+ try {
29611
+ const resetResult = await client.query(`
29612
+ select max(extract(epoch from now() - stats_reset)::int) as stats_reset_s
29613
+ from pg_stat_io
29614
+ `);
29615
+ if (resetResult.rows.length > 0 && resetResult.rows[0].stats_reset_s !== null) {
29616
+ const parsedStatsResetS = parseInt(resetResult.rows[0].stats_reset_s, 10);
29617
+ statsResetS = Number.isFinite(parsedStatsResetS) ? parsedStatsResetS : null;
29618
+ }
29619
+ } catch (err) {}
28269
29620
  report.results[nodeName] = {
28270
29621
  data: {
28271
- settings: lockSettings,
28272
- deadlock_stats: deadlockStats,
28273
- ...deadlockError && { deadlock_stats_error: deadlockError }
29622
+ available: ioStats.length > 0,
29623
+ by_backend_type: ioStats,
29624
+ analysis: {
29625
+ total_read_mb: totalReadMb,
29626
+ total_write_mb: totalWriteMb,
29627
+ total_io_time_ms: totalIoTimeMs,
29628
+ read_hit_ratio_pct: readHitRatioPct,
29629
+ avg_read_time_ms: avgReadTimeMs,
29630
+ avg_write_time_ms: avgWriteTimeMs
29631
+ },
29632
+ stats_reset_s: statsResetS
28274
29633
  },
28275
29634
  postgres_version: postgresVersion
28276
29635
  };
@@ -28291,7 +29650,8 @@ var REPORT_GENERATORS = {
28291
29650
  G003: generateG003,
28292
29651
  H001: generateH001,
28293
29652
  H002: generateH002,
28294
- H004: generateH004
29653
+ H004: generateH004,
29654
+ I001: generateI001
28295
29655
  };
28296
29656
  var CHECK_INFO = (() => {
28297
29657
  const fullMap = buildCheckInfoMap();
@@ -28327,6 +29687,7 @@ function getCheckupEntry(code) {
28327
29687
  }
28328
29688
 
28329
29689
  // lib/checkup-api.ts
29690
+ import * as http2 from "http";
28330
29691
  import * as https from "https";
28331
29692
  import { URL as URL3 } from "url";
28332
29693
  var DEFAULT_RETRY_CONFIG = {
@@ -28369,7 +29730,7 @@ async function withRetry(fn, config2 = {}, onRetry) {
28369
29730
  if (onRetry) {
28370
29731
  onRetry(attempt, err, delayMs);
28371
29732
  }
28372
- await new Promise((resolve5) => setTimeout(resolve5, delayMs));
29733
+ await new Promise((resolve6) => setTimeout(resolve6, delayMs));
28373
29734
  delayMs = Math.min(delayMs * backoffMultiplier, maxDelayMs);
28374
29735
  }
28375
29736
  }
@@ -28439,7 +29800,7 @@ async function postRpc(params) {
28439
29800
  const controller = new AbortController;
28440
29801
  let timeoutId = null;
28441
29802
  let settled = false;
28442
- return new Promise((resolve5, reject) => {
29803
+ return new Promise((resolve6, reject) => {
28443
29804
  const settledReject = (err) => {
28444
29805
  if (settled)
28445
29806
  return;
@@ -28454,9 +29815,17 @@ async function postRpc(params) {
28454
29815
  settled = true;
28455
29816
  if (timeoutId)
28456
29817
  clearTimeout(timeoutId);
28457
- resolve5(value);
29818
+ resolve6(value);
28458
29819
  };
28459
- const req = https.request(url, {
29820
+ if (url.protocol === "http:") {
29821
+ const hostname = url.hostname.replace(/^\[|\]$/g, "");
29822
+ const isLoopback = ["localhost", "127.0.0.1", "::1"].includes(hostname);
29823
+ if (!isLoopback && process.env.CHECKUP_ALLOW_HTTP !== "1") {
29824
+ throw new Error(`Refusing to send API key over plaintext HTTP to '${url.host}'. ` + `Use https://, a loopback hostname, or set CHECKUP_ALLOW_HTTP=1.`);
29825
+ }
29826
+ }
29827
+ const transport = url.protocol === "http:" ? http2 : https;
29828
+ const req = transport.request(url, {
28460
29829
  method: "POST",
28461
29830
  headers,
28462
29831
  signal: controller.signal
@@ -28826,28 +30195,23 @@ function closeReadline() {
28826
30195
  rl = null;
28827
30196
  }
28828
30197
  }
28829
- async function execPromise(command) {
28830
- return new Promise((resolve6, reject) => {
28831
- childProcess.exec(command, (error2, stdout, stderr) => {
28832
- if (error2) {
28833
- const err = error2;
28834
- err.code = typeof error2.code === "number" ? error2.code : 1;
28835
- reject(err);
28836
- } else {
28837
- resolve6({ stdout, stderr });
28838
- }
28839
- });
28840
- });
30198
+ function stripMatchingQuotes(value) {
30199
+ const trimmed = value.trim();
30200
+ const quote = trimmed[0];
30201
+ if (trimmed.length >= 2 && (quote === '"' || quote === "'") && trimmed.endsWith(quote)) {
30202
+ return trimmed.slice(1, -1);
30203
+ }
30204
+ return trimmed;
28841
30205
  }
28842
30206
  async function execFilePromise(file, args) {
28843
- return new Promise((resolve6, reject) => {
30207
+ return new Promise((resolve7, reject) => {
28844
30208
  childProcess.execFile(file, args, (error2, stdout, stderr) => {
28845
30209
  if (error2) {
28846
30210
  const err = error2;
28847
30211
  err.code = typeof error2.code === "number" ? error2.code : 1;
28848
30212
  reject(err);
28849
30213
  } else {
28850
- resolve6({ stdout, stderr });
30214
+ resolve7({ stdout, stderr });
28851
30215
  }
28852
30216
  });
28853
30217
  });
@@ -28888,9 +30252,9 @@ function spawn2(cmd, args, options) {
28888
30252
  };
28889
30253
  }
28890
30254
  async function question(prompt) {
28891
- return new Promise((resolve6) => {
30255
+ return new Promise((resolve7) => {
28892
30256
  getReadline().question(prompt, (answer) => {
28893
- resolve6(answer);
30257
+ resolve7(answer);
28894
30258
  });
28895
30259
  });
28896
30260
  }
@@ -28901,7 +30265,7 @@ function expandHomePath(p) {
28901
30265
  if (s === "~")
28902
30266
  return os3.homedir();
28903
30267
  if (s.startsWith("~/") || s.startsWith("~\\")) {
28904
- return path5.join(os3.homedir(), s.slice(2));
30268
+ return path6.join(os3.homedir(), s.slice(2));
28905
30269
  }
28906
30270
  return s;
28907
30271
  }
@@ -28950,10 +30314,10 @@ function prepareOutputDirectory(outputOpt) {
28950
30314
  if (!outputOpt)
28951
30315
  return;
28952
30316
  const outputDir = expandHomePath(outputOpt);
28953
- const outputPath = path5.isAbsolute(outputDir) ? outputDir : path5.resolve(process.cwd(), outputDir);
28954
- if (!fs5.existsSync(outputPath)) {
30317
+ const outputPath = path6.isAbsolute(outputDir) ? outputDir : path6.resolve(process.cwd(), outputDir);
30318
+ if (!fs6.existsSync(outputPath)) {
28955
30319
  try {
28956
- fs5.mkdirSync(outputPath, { recursive: true });
30320
+ fs6.mkdirSync(outputPath, { recursive: true });
28957
30321
  } catch (e) {
28958
30322
  const errAny = e;
28959
30323
  const code = typeof errAny?.code === "string" ? errAny.code : "";
@@ -29032,9 +30396,10 @@ async function uploadCheckupReports(uploadCfg, reports, spinner, logUpload) {
29032
30396
  }
29033
30397
  function writeReportFiles(reports, outputPath) {
29034
30398
  for (const [checkId, report] of Object.entries(reports)) {
29035
- const filePath = path5.join(outputPath, `${checkId}.json`);
29036
- fs5.writeFileSync(filePath, JSON.stringify(report, null, 2), "utf8");
29037
- console.log(`\u2713 ${checkId}: ${filePath}`);
30399
+ const filePath = path6.join(outputPath, `${checkId}.json`);
30400
+ fs6.writeFileSync(filePath, JSON.stringify(report, null, 2), "utf8");
30401
+ const title = report.checkTitle || checkId;
30402
+ console.log(`\u2713 ${checkId} ${title}: ${filePath}`);
29038
30403
  }
29039
30404
  }
29040
30405
  function printUploadSummary(summary, projectWasGenerated, useStderr, reports) {
@@ -29077,7 +30442,7 @@ function getDefaultMonitoringProjectDir() {
29077
30442
  const override = process.env.PGAI_PROJECT_DIR;
29078
30443
  if (override && override.trim())
29079
30444
  return override.trim();
29080
- return path5.join(getConfigDir(), "monitoring");
30445
+ return path6.join(getConfigDir(), "monitoring");
29081
30446
  }
29082
30447
  async function downloadText(url) {
29083
30448
  const controller = new AbortController;
@@ -29094,12 +30459,12 @@ async function downloadText(url) {
29094
30459
  }
29095
30460
  async function ensureDefaultMonitoringProject() {
29096
30461
  const projectDir = getDefaultMonitoringProjectDir();
29097
- const composeFile = path5.resolve(projectDir, "docker-compose.yml");
29098
- const instancesFile = path5.resolve(projectDir, "instances.yml");
29099
- if (!fs5.existsSync(projectDir)) {
29100
- fs5.mkdirSync(projectDir, { recursive: true, mode: 448 });
30462
+ const composeFile = path6.resolve(projectDir, "docker-compose.yml");
30463
+ const instancesFile = path6.resolve(projectDir, "instances.yml");
30464
+ if (!fs6.existsSync(projectDir)) {
30465
+ fs6.mkdirSync(projectDir, { recursive: true, mode: 448 });
29101
30466
  }
29102
- if (!fs5.existsSync(composeFile)) {
30467
+ if (!fs6.existsSync(composeFile)) {
29103
30468
  const refs = [
29104
30469
  process.env.PGAI_PROJECT_REF,
29105
30470
  package_default.version,
@@ -29111,36 +30476,39 @@ async function ensureDefaultMonitoringProject() {
29111
30476
  const url = `https://gitlab.com/postgres-ai/postgres_ai/-/raw/${encodeURIComponent(ref)}/docker-compose.yml`;
29112
30477
  try {
29113
30478
  const text = await downloadText(url);
29114
- fs5.writeFileSync(composeFile, text, { encoding: "utf8", mode: 384 });
30479
+ fs6.writeFileSync(composeFile, text, { encoding: "utf8", mode: 384 });
29115
30480
  break;
29116
30481
  } catch (err) {
29117
30482
  lastErr = err;
29118
30483
  }
29119
30484
  }
29120
- if (!fs5.existsSync(composeFile)) {
30485
+ if (!fs6.existsSync(composeFile)) {
29121
30486
  const msg = lastErr instanceof Error ? lastErr.message : String(lastErr);
29122
30487
  throw new Error(`Failed to bootstrap docker-compose.yml: ${msg}`);
29123
30488
  }
29124
30489
  }
29125
- if (!fs5.existsSync(instancesFile)) {
30490
+ if (fs6.existsSync(instancesFile) && fs6.lstatSync(instancesFile).isDirectory()) {
30491
+ fs6.rmSync(instancesFile, { recursive: true, force: true });
30492
+ }
30493
+ if (!fs6.existsSync(instancesFile)) {
29126
30494
  const header = `# PostgreSQL instances to monitor
29127
30495
  ` + `# Add your instances using: pgai mon targets add <connection-string> <name>
29128
30496
 
29129
30497
  `;
29130
- fs5.writeFileSync(instancesFile, header, { encoding: "utf8", mode: 384 });
30498
+ fs6.writeFileSync(instancesFile, header, { encoding: "utf8", mode: 384 });
29131
30499
  }
29132
- const pgwatchConfig = path5.resolve(projectDir, ".pgwatch-config");
29133
- if (!fs5.existsSync(pgwatchConfig)) {
29134
- fs5.writeFileSync(pgwatchConfig, "", { encoding: "utf8", mode: 384 });
30500
+ const pgwatchConfig = path6.resolve(projectDir, ".pgwatch-config");
30501
+ if (!fs6.existsSync(pgwatchConfig)) {
30502
+ fs6.writeFileSync(pgwatchConfig, "", { encoding: "utf8", mode: 384 });
29135
30503
  }
29136
- const envFile = path5.resolve(projectDir, ".env");
29137
- if (!fs5.existsSync(envFile)) {
30504
+ const envFile = path6.resolve(projectDir, ".env");
30505
+ if (!fs6.existsSync(envFile)) {
29138
30506
  const envText = `PGAI_TAG=${package_default.version}
29139
30507
  # PGAI_REGISTRY=registry.gitlab.com/postgres-ai/postgres_ai
29140
30508
  `;
29141
- fs5.writeFileSync(envFile, envText, { encoding: "utf8", mode: 384 });
30509
+ fs6.writeFileSync(envFile, envText, { encoding: "utf8", mode: 384 });
29142
30510
  }
29143
- return { fs: fs5, path: path5, projectDir, composeFile, instancesFile };
30511
+ return { fs: fs6, path: path6, projectDir, composeFile, instancesFile };
29144
30512
  }
29145
30513
  function getConfig(opts) {
29146
30514
  let apiKey = opts.apiKey || process.env.PGAI_API_KEY || "";
@@ -29171,7 +30539,7 @@ function printResult(result, json2) {
29171
30539
  }
29172
30540
  }
29173
30541
  var program2 = new Command;
29174
- program2.name("postgres-ai").description("PostgresAI CLI").version(package_default.version).option("--api-key <key>", "API key (overrides PGAI_API_KEY)").option("--api-base-url <url>", "API base URL for backend RPC (overrides PGAI_API_BASE_URL)").option("--ui-base-url <url>", "UI base URL for browser routes (overrides PGAI_UI_BASE_URL)");
30542
+ program2.name("postgres-ai").description("PostgresAI CLI").version(package_default.version).option("--api-key <key>", "API key (overrides PGAI_API_KEY)").option("--api-base-url <url>", "API base URL for backend RPC (overrides PGAI_API_BASE_URL)").option("--ui-base-url <url>", "UI base URL for browser routes (overrides PGAI_UI_BASE_URL)").option("--storage-base-url <url>", "Storage base URL for file uploads (overrides PGAI_STORAGE_BASE_URL)");
29175
30543
  program2.command("set-default-project <project>").description("store default project for checkup uploads").action(async (project) => {
29176
30544
  const value = (project || "").trim();
29177
30545
  if (!value) {
@@ -29182,6 +30550,23 @@ program2.command("set-default-project <project>").description("store default pro
29182
30550
  writeConfig({ defaultProject: value });
29183
30551
  console.log(`Default project saved: ${value}`);
29184
30552
  });
30553
+ program2.command("set-storage-url <url>").description("store storage base URL for file uploads").action(async (url) => {
30554
+ const value = (url || "").trim();
30555
+ if (!value) {
30556
+ console.error("Error: url is required");
30557
+ process.exitCode = 1;
30558
+ return;
30559
+ }
30560
+ try {
30561
+ const { normalizeBaseUrl: normalizeBaseUrl3 } = await Promise.resolve().then(() => (init_util(), exports_util2));
30562
+ const normalized = normalizeBaseUrl3(value);
30563
+ writeConfig({ storageBaseUrl: normalized });
30564
+ console.log(`Storage URL saved: ${normalized}`);
30565
+ } catch {
30566
+ console.error(`Error: invalid URL: ${value}`);
30567
+ process.exitCode = 1;
30568
+ }
30569
+ });
29185
30570
  program2.command("prepare-db [conn]").description("prepare database for monitoring: create monitoring user, required view(s), and grant permissions (idempotent)").option("--db-url <url>", "PostgreSQL connection URL (admin) to run the setup against (deprecated; pass it as positional arg)").option("-h, --host <host>", "PostgreSQL host (psql-like)").option("-p, --port <port>", "PostgreSQL port (psql-like)").option("-U, --username <username>", "PostgreSQL user (psql-like)").option("-d, --dbname <dbname>", "PostgreSQL database name (psql-like)").option("--admin-password <password>", "Admin connection password (otherwise uses PGPASSWORD if set)").option("--monitoring-user <name>", "Monitoring role name to create/update", DEFAULT_MONITORING_USER).option("--password <password>", "Monitoring role password (overrides PGAI_MON_PASSWORD)").option("--skip-optional-permissions", "Skip optional permissions (RDS/self-managed extras)", false).option("--provider <provider>", "Database provider (e.g., supabase). Affects which steps are executed.").option("--verify", "Verify that monitoring role/permissions are in place (no changes)", false).option("--reset-password", "Reset monitoring role password only (no other changes)", false).option("--print-sql", "Print SQL plan and exit (no changes applied)", false).option("--print-password", "Print generated monitoring password (DANGEROUS in CI logs)", false).option("--supabase", "Use Supabase Management API instead of direct PostgreSQL connection", false).option("--supabase-access-token <token>", "Supabase Management API access token (or SUPABASE_ACCESS_TOKEN env)").option("--supabase-project-ref <ref>", "Supabase project reference (or SUPABASE_PROJECT_REF env)").option("--json", "Output result as JSON (machine-readable)", false).addHelpText("after", [
29186
30571
  "",
29187
30572
  "Examples:",
@@ -29362,9 +30747,9 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
29362
30747
  } else {
29363
30748
  console.log("\u2713 prepare-db verify: OK");
29364
30749
  if (v.missingOptional.length > 0) {
29365
- console.log("\u26A0 Optional items missing:");
30750
+ console.error("\u26A0 Optional items missing:");
29366
30751
  for (const m of v.missingOptional)
29367
- console.log(`- ${m}`);
30752
+ console.error(`- ${m}`);
29368
30753
  }
29369
30754
  }
29370
30755
  return;
@@ -29485,9 +30870,9 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
29485
30870
  } else {
29486
30871
  console.log(opts.resetPassword ? "\u2713 prepare-db password reset completed" : "\u2713 prepare-db completed");
29487
30872
  if (skippedOptional.length > 0) {
29488
- console.log("\u26A0 Some optional steps were skipped (not supported or insufficient privileges):");
30873
+ console.error("\u26A0 Some optional steps were skipped (not supported or insufficient privileges):");
29489
30874
  for (const s of skippedOptional)
29490
- console.log(`- ${s}`);
30875
+ console.error(`- ${s}`);
29491
30876
  }
29492
30877
  if (process.stdout.isTTY) {
29493
30878
  console.log(`Applied ${applied.length} steps`);
@@ -29645,9 +31030,9 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
29645
31030
  } else {
29646
31031
  console.log(`\u2713 prepare-db verify: OK${opts.provider ? ` (provider: ${opts.provider})` : ""}`);
29647
31032
  if (v.missingOptional.length > 0) {
29648
- console.log("\u26A0 Optional items missing:");
31033
+ console.error("\u26A0 Optional items missing:");
29649
31034
  for (const m of v.missingOptional)
29650
- console.log(`- ${m}`);
31035
+ console.error(`- ${m}`);
29651
31036
  }
29652
31037
  }
29653
31038
  return;
@@ -29763,9 +31148,9 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
29763
31148
  } else {
29764
31149
  console.log(opts.resetPassword ? "\u2713 prepare-db password reset completed" : "\u2713 prepare-db completed");
29765
31150
  if (skippedOptional.length > 0) {
29766
- console.log("\u26A0 Some optional steps were skipped (not supported or insufficient privileges):");
31151
+ console.error("\u26A0 Some optional steps were skipped (not supported or insufficient privileges):");
29767
31152
  for (const s of skippedOptional)
29768
- console.log(`- ${s}`);
31153
+ console.error(`- ${s}`);
29769
31154
  }
29770
31155
  if (process.stdout.isTTY) {
29771
31156
  console.log(`Applied ${applied.length} steps`);
@@ -29965,9 +31350,9 @@ program2.command("unprepare-db [conn]").description("remove monitoring setup: dr
29965
31350
  console.log(`Drop role: ${dropRole}`);
29966
31351
  }
29967
31352
  if (!opts.force && !jsonOutput && !shouldPrintSql) {
29968
- const answer = await new Promise((resolve6) => {
31353
+ const answer = await new Promise((resolve7) => {
29969
31354
  const readline = getReadline();
29970
- readline.question(`This will remove the monitoring setup for user "${opts.monitoringUser}"${dropRole ? " and drop the role" : ""}. Continue? [y/N] `, (ans) => resolve6(ans.trim().toLowerCase()));
31355
+ readline.question(`This will remove the monitoring setup for user "${opts.monitoringUser}"${dropRole ? " and drop the role" : ""}. Continue? [y/N] `, (ans) => resolve7(ans.trim().toLowerCase()));
29971
31356
  });
29972
31357
  if (answer !== "y" && answer !== "yes") {
29973
31358
  console.log("Aborted.");
@@ -30021,11 +31406,11 @@ program2.command("unprepare-db [conn]").description("remove monitoring setup: dr
30021
31406
  console.log("\u2713 unprepare-db completed");
30022
31407
  console.log(`Applied ${applied.length} steps`);
30023
31408
  } else {
30024
- console.log("\u26A0 unprepare-db completed with errors");
31409
+ console.error("\u26A0 unprepare-db completed with errors");
30025
31410
  console.log(`Applied ${applied.length} steps`);
30026
- console.log("Errors:");
31411
+ console.error("Errors:");
30027
31412
  for (const err of errors3) {
30028
- console.log(` - ${err}`);
31413
+ console.error(` - ${err}`);
30029
31414
  }
30030
31415
  process.exitCode = 1;
30031
31416
  }
@@ -30174,6 +31559,20 @@ Usage: postgresai checkup ${checkId} postgresql://user@host:5432/dbname
30174
31559
  spinner.update("Connecting to Postgres");
30175
31560
  const connResult = await connectWithSslFallback(Client, adminConn);
30176
31561
  client = connResult.client;
31562
+ spinner.update("Checking database permissions");
31563
+ const permCheck = await checkCurrentUserPermissions(client);
31564
+ const permMessages = formatPermissionCheckMessages(permCheck);
31565
+ for (const w of permMessages.warnings) {
31566
+ console.error(w);
31567
+ }
31568
+ if (permMessages.failed) {
31569
+ spinner.stop();
31570
+ for (const e of permMessages.errors) {
31571
+ console.error(e);
31572
+ }
31573
+ process.exitCode = 1;
31574
+ return;
31575
+ }
30177
31576
  let reports;
30178
31577
  if (checkId === "ALL") {
30179
31578
  reports = await generateAllReports(client, opts.nodeName, (p) => {
@@ -30271,7 +31670,7 @@ Usage: postgresai checkup ${checkId} postgresql://user@host:5432/dbname
30271
31670
  }
30272
31671
  }
30273
31672
  }
30274
- if (shouldPrintJson) {
31673
+ if (shouldPrintJson && !outputPath) {
30275
31674
  console.log(JSON.stringify(reports, null, 2));
30276
31675
  }
30277
31676
  const hadOutput = shouldPrintJson || shouldConvertMarkdown || outputPath || uploadSummary;
@@ -30324,12 +31723,12 @@ function resolvePaths() {
30324
31723
  const startDir = process.cwd();
30325
31724
  let currentDir = startDir;
30326
31725
  while (true) {
30327
- const composeFile = path5.resolve(currentDir, "docker-compose.yml");
30328
- if (fs5.existsSync(composeFile)) {
30329
- const instancesFile = path5.resolve(currentDir, "instances.yml");
30330
- return { fs: fs5, path: path5, projectDir: currentDir, composeFile, instancesFile };
31726
+ const composeFile = path6.resolve(currentDir, "docker-compose.yml");
31727
+ if (fs6.existsSync(composeFile)) {
31728
+ const instancesFile = path6.resolve(currentDir, "instances.yml");
31729
+ return { fs: fs6, path: path6, projectDir: currentDir, composeFile, instancesFile };
30331
31730
  }
30332
- const parentDir = path5.dirname(currentDir);
31731
+ const parentDir = path6.dirname(currentDir);
30333
31732
  if (parentDir === currentDir)
30334
31733
  break;
30335
31734
  currentDir = parentDir;
@@ -30353,10 +31752,10 @@ function isDockerRunning() {
30353
31752
  }
30354
31753
  function getComposeCmd() {
30355
31754
  const tryCmd = (cmd, args) => spawnSync2(cmd, args, { stdio: "ignore", timeout: 5000 }).status === 0;
30356
- if (tryCmd("docker-compose", ["version"]))
30357
- return ["docker-compose"];
30358
31755
  if (tryCmd("docker", ["compose", "version"]))
30359
31756
  return ["docker", "compose"];
31757
+ if (tryCmd("docker-compose", ["version"]))
31758
+ return ["docker-compose"];
30360
31759
  return null;
30361
31760
  }
30362
31761
  function checkRunningContainers() {
@@ -30377,10 +31776,10 @@ function registerMonitoringInstance(apiKey, projectName, opts) {
30377
31776
  const url = `${apiBaseUrl}/rpc/monitoring_instance_register`;
30378
31777
  const debug = opts?.debug;
30379
31778
  if (debug) {
30380
- console.log(`
31779
+ console.error(`
30381
31780
  Debug: Registering monitoring instance...`);
30382
- console.log(`Debug: POST ${url}`);
30383
- console.log(`Debug: project_name=${projectName}`);
31781
+ console.error(`Debug: POST ${url}`);
31782
+ console.error(`Debug: project_name=${projectName}`);
30384
31783
  }
30385
31784
  fetch(url, {
30386
31785
  method: "POST",
@@ -30395,26 +31794,26 @@ Debug: Registering monitoring instance...`);
30395
31794
  const body = await res.text().catch(() => "");
30396
31795
  if (!res.ok) {
30397
31796
  if (debug) {
30398
- console.log(`Debug: Monitoring registration failed: HTTP ${res.status}`);
30399
- console.log(`Debug: Response: ${body}`);
31797
+ console.error(`Debug: Monitoring registration failed: HTTP ${res.status}`);
31798
+ console.error(`Debug: Response: ${body}`);
30400
31799
  }
30401
31800
  return;
30402
31801
  }
30403
31802
  if (debug) {
30404
- console.log(`Debug: Monitoring registration response: ${body}`);
31803
+ console.error(`Debug: Monitoring registration response: ${body}`);
30405
31804
  }
30406
31805
  }).catch((err) => {
30407
31806
  if (debug) {
30408
- console.log(`Debug: Monitoring registration error: ${err.message}`);
31807
+ console.error(`Debug: Monitoring registration error: ${err.message}`);
30409
31808
  }
30410
31809
  });
30411
31810
  }
30412
31811
  function updatePgwatchConfig(configPath, updates) {
30413
31812
  let lines = [];
30414
- if (fs5.existsSync(configPath)) {
30415
- const stats = fs5.statSync(configPath);
31813
+ if (fs6.existsSync(configPath)) {
31814
+ const stats = fs6.statSync(configPath);
30416
31815
  if (!stats.isDirectory()) {
30417
- const content = fs5.readFileSync(configPath, "utf8");
31816
+ const content = fs6.readFileSync(configPath, "utf8");
30418
31817
  lines = content.split(/\r?\n/).filter((l) => l.trim() !== "");
30419
31818
  }
30420
31819
  }
@@ -30426,7 +31825,7 @@ function updatePgwatchConfig(configPath, updates) {
30426
31825
  lines.push(`${key}=${value}`);
30427
31826
  }
30428
31827
  }
30429
- fs5.writeFileSync(configPath, lines.join(`
31828
+ fs6.writeFileSync(configPath, lines.join(`
30430
31829
  `) + `
30431
31830
  `, { encoding: "utf8", mode: 384 });
30432
31831
  }
@@ -30456,12 +31855,12 @@ async function runCompose(args, grafanaPassword) {
30456
31855
  if (grafanaPassword) {
30457
31856
  env.GF_SECURITY_ADMIN_PASSWORD = grafanaPassword;
30458
31857
  } else {
30459
- const cfgPath = path5.resolve(projectDir, ".pgwatch-config");
30460
- if (fs5.existsSync(cfgPath)) {
31858
+ const cfgPath = path6.resolve(projectDir, ".pgwatch-config");
31859
+ if (fs6.existsSync(cfgPath)) {
30461
31860
  try {
30462
- const stats = fs5.statSync(cfgPath);
31861
+ const stats = fs6.statSync(cfgPath);
30463
31862
  if (!stats.isDirectory()) {
30464
- const content = fs5.readFileSync(cfgPath, "utf8");
31863
+ const content = fs6.readFileSync(cfgPath, "utf8");
30465
31864
  const match = content.match(/^grafana_password=([^\r\n]+)/m);
30466
31865
  if (match) {
30467
31866
  env.GF_SECURITY_ADMIN_PASSWORD = match[1].trim();
@@ -30474,17 +31873,37 @@ async function runCompose(args, grafanaPassword) {
30474
31873
  }
30475
31874
  }
30476
31875
  }
31876
+ const envFilePath = path6.resolve(projectDir, ".env");
31877
+ if (fs6.existsSync(envFilePath)) {
31878
+ try {
31879
+ const envContent = fs6.readFileSync(envFilePath, "utf8");
31880
+ if (!env.VM_AUTH_USERNAME) {
31881
+ const m = envContent.match(/^VM_AUTH_USERNAME=([^\r\n]+)/m);
31882
+ if (m)
31883
+ env.VM_AUTH_USERNAME = stripMatchingQuotes(m[1]);
31884
+ }
31885
+ if (!env.VM_AUTH_PASSWORD) {
31886
+ const m = envContent.match(/^VM_AUTH_PASSWORD=([^\r\n]+)/m);
31887
+ if (m)
31888
+ env.VM_AUTH_PASSWORD = stripMatchingQuotes(m[1]);
31889
+ }
31890
+ } catch (err) {
31891
+ if (process.env.DEBUG) {
31892
+ console.warn(`Warning: Could not read VM auth from .env: ${err instanceof Error ? err.message : String(err)}`);
31893
+ }
31894
+ }
31895
+ }
30477
31896
  const finalArgs = [...args];
30478
31897
  if (process.platform === "darwin" && args.includes("up")) {
30479
31898
  finalArgs.push("--scale", "self-node-exporter=0");
30480
31899
  }
30481
- return new Promise((resolve6) => {
31900
+ return new Promise((resolve7) => {
30482
31901
  const child = spawn2(cmd[0], [...cmd.slice(1), "-f", composeFile, ...finalArgs], {
30483
31902
  stdio: "inherit",
30484
31903
  env,
30485
31904
  cwd: projectDir
30486
31905
  });
30487
- child.on("close", (code) => resolve6(code || 0));
31906
+ child.on("close", (code) => resolve7(code || 0));
30488
31907
  });
30489
31908
  }
30490
31909
  program2.command("help", { isDefault: true }).description("show help").action(() => {
@@ -30501,26 +31920,38 @@ mon.command("local-install").description("install local monitoring stack (genera
30501
31920
  `);
30502
31921
  console.log(`This will install, configure, and start the monitoring system
30503
31922
  `);
30504
- const { projectDir } = await resolveOrInitPaths();
31923
+ const { projectDir, instancesFile: instancesPath } = await resolveOrInitPaths();
30505
31924
  console.log(`Project directory: ${projectDir}
30506
31925
  `);
30507
31926
  if (opts.project) {
30508
- const cfgPath2 = path5.resolve(projectDir, ".pgwatch-config");
31927
+ const cfgPath2 = path6.resolve(projectDir, ".pgwatch-config");
30509
31928
  updatePgwatchConfig(cfgPath2, { project_name: opts.project });
30510
31929
  console.log(`Using project name: ${opts.project}
30511
31930
  `);
30512
31931
  }
30513
- const envFile = path5.resolve(projectDir, ".env");
31932
+ const envFile = path6.resolve(projectDir, ".env");
30514
31933
  let existingRegistry = null;
30515
31934
  let existingPassword = null;
30516
- if (fs5.existsSync(envFile)) {
30517
- const existingEnv = fs5.readFileSync(envFile, "utf8");
31935
+ let existingReplicatorPassword = null;
31936
+ let existingVmAuthUsername = null;
31937
+ let existingVmAuthPassword = null;
31938
+ if (fs6.existsSync(envFile)) {
31939
+ const existingEnv = fs6.readFileSync(envFile, "utf8");
30518
31940
  const registryMatch = existingEnv.match(/^PGAI_REGISTRY=(.+)$/m);
30519
31941
  if (registryMatch)
30520
31942
  existingRegistry = registryMatch[1].trim();
30521
31943
  const pwdMatch = existingEnv.match(/^GF_SECURITY_ADMIN_PASSWORD=(.+)$/m);
30522
31944
  if (pwdMatch)
30523
31945
  existingPassword = pwdMatch[1].trim();
31946
+ const replicatorPwdMatch = existingEnv.match(/^REPLICATOR_PASSWORD=(.+)$/m);
31947
+ if (replicatorPwdMatch)
31948
+ existingReplicatorPassword = replicatorPwdMatch[1].trim();
31949
+ const vmAuthUserMatch = existingEnv.match(/^VM_AUTH_USERNAME=(.+)$/m);
31950
+ if (vmAuthUserMatch)
31951
+ existingVmAuthUsername = stripMatchingQuotes(vmAuthUserMatch[1]);
31952
+ const vmAuthPasswordMatch = existingEnv.match(/^VM_AUTH_PASSWORD=(.+)$/m);
31953
+ if (vmAuthPasswordMatch)
31954
+ existingVmAuthPassword = stripMatchingQuotes(vmAuthPasswordMatch[1]);
30524
31955
  }
30525
31956
  const imageTag = opts.tag || package_default.version;
30526
31957
  const envLines = [`PGAI_TAG=${imageTag}`];
@@ -30530,7 +31961,10 @@ mon.command("local-install").description("install local monitoring stack (genera
30530
31961
  if (existingPassword) {
30531
31962
  envLines.push(`GF_SECURITY_ADMIN_PASSWORD=${existingPassword}`);
30532
31963
  }
30533
- fs5.writeFileSync(envFile, envLines.join(`
31964
+ envLines.push(`REPLICATOR_PASSWORD=${existingReplicatorPassword || crypto2.randomBytes(32).toString("hex")}`);
31965
+ envLines.push(`VM_AUTH_USERNAME=${existingVmAuthUsername || "vmauth"}`);
31966
+ envLines.push(`VM_AUTH_PASSWORD=${existingVmAuthPassword || crypto2.randomBytes(18).toString("base64")}`);
31967
+ fs6.writeFileSync(envFile, envLines.join(`
30534
31968
  `) + `
30535
31969
  `, { encoding: "utf8", mode: 384 });
30536
31970
  if (opts.tag) {
@@ -30538,8 +31972,8 @@ mon.command("local-install").description("install local monitoring stack (genera
30538
31972
  `);
30539
31973
  }
30540
31974
  if (opts.demo && opts.dbUrl) {
30541
- console.log("\u26A0 Both --demo and --db-url provided. Demo mode includes its own database.");
30542
- console.log(`\u26A0 The --db-url will be ignored in demo mode.
31975
+ console.error("\u26A0 Both --demo and --db-url provided. Demo mode includes its own database.");
31976
+ console.error(`\u26A0 The --db-url will be ignored in demo mode.
30543
31977
  `);
30544
31978
  opts.dbUrl = undefined;
30545
31979
  }
@@ -30554,7 +31988,7 @@ Use demo mode without API key: postgres-ai mon local-install --demo`);
30554
31988
  }
30555
31989
  const { running, containers } = checkRunningContainers();
30556
31990
  if (running) {
30557
- console.log(`\u26A0 Monitoring services are already running: ${containers.join(", ")}`);
31991
+ console.error(`\u26A0 Monitoring services are already running: ${containers.join(", ")}`);
30558
31992
  console.log(`Use 'postgres-ai mon restart' to restart them
30559
31993
  `);
30560
31994
  return;
@@ -30566,12 +32000,12 @@ Use demo mode without API key: postgres-ai mon local-install --demo`);
30566
32000
  if (apiKey) {
30567
32001
  console.log("Using API key provided via --api-key parameter");
30568
32002
  writeConfig({ apiKey });
30569
- updatePgwatchConfig(path5.resolve(projectDir, ".pgwatch-config"), { api_key: apiKey });
32003
+ updatePgwatchConfig(path6.resolve(projectDir, ".pgwatch-config"), { api_key: apiKey });
30570
32004
  console.log(`\u2713 API key saved
30571
32005
  `);
30572
32006
  } else if (opts.yes) {
30573
32007
  console.log("Auto-yes mode: no API key provided, skipping API key setup");
30574
- console.log("\u26A0 Reports will be generated locally only");
32008
+ console.error("\u26A0 Reports will be generated locally only");
30575
32009
  console.log(`You can add an API key later with: postgres-ai add-key <api_key>
30576
32010
  `);
30577
32011
  } else {
@@ -30583,23 +32017,23 @@ Use demo mode without API key: postgres-ai mon local-install --demo`);
30583
32017
  const trimmedKey = inputApiKey.trim();
30584
32018
  if (trimmedKey) {
30585
32019
  writeConfig({ apiKey: trimmedKey });
30586
- updatePgwatchConfig(path5.resolve(projectDir, ".pgwatch-config"), { api_key: trimmedKey });
32020
+ updatePgwatchConfig(path6.resolve(projectDir, ".pgwatch-config"), { api_key: trimmedKey });
30587
32021
  apiKey = trimmedKey;
30588
32022
  console.log(`\u2713 API key saved
30589
32023
  `);
30590
32024
  break;
30591
32025
  }
30592
- console.log("\u26A0 API key cannot be empty");
32026
+ console.error("\u26A0 API key cannot be empty");
30593
32027
  const retry = await question("Try again or skip API key setup, retry? (Y/n): ");
30594
32028
  if (retry.toLowerCase() === "n") {
30595
- console.log("\u26A0 Skipping API key setup - reports will be generated locally only");
32029
+ console.error("\u26A0 Skipping API key setup - reports will be generated locally only");
30596
32030
  console.log(`You can add an API key later with: postgres-ai add-key <api_key>
30597
32031
  `);
30598
32032
  break;
30599
32033
  }
30600
32034
  }
30601
32035
  } else {
30602
- console.log("\u26A0 Skipping API key setup - reports will be generated locally only");
32036
+ console.error("\u26A0 Skipping API key setup - reports will be generated locally only");
30603
32037
  console.log(`You can add an API key later with: postgres-ai add-key <api_key>
30604
32038
  `);
30605
32039
  }
@@ -30612,13 +32046,13 @@ Use demo mode without API key: postgres-ai mon local-install --demo`);
30612
32046
  if (!opts.demo) {
30613
32047
  console.log(`Step 2: Add PostgreSQL Instance to Monitor
30614
32048
  `);
30615
- const { instancesFile: instancesPath, projectDir: projectDir2 } = await resolveOrInitPaths();
32049
+ const { instancesFile: instancesPath2, projectDir: projectDir2 } = await resolveOrInitPaths();
30616
32050
  const emptyInstancesContent = `# PostgreSQL instances to monitor
30617
32051
  # Add your instances using: postgres-ai mon targets add
30618
32052
 
30619
32053
  `;
30620
- fs5.writeFileSync(instancesPath, emptyInstancesContent, "utf8");
30621
- console.log(`Instances file: ${instancesPath}`);
32054
+ fs6.writeFileSync(instancesPath2, emptyInstancesContent, "utf8");
32055
+ console.log(`Instances file: ${instancesPath2}`);
30622
32056
  console.log(`Project directory: ${projectDir2}
30623
32057
  `);
30624
32058
  if (opts.dbUrl) {
@@ -30649,26 +32083,31 @@ Use demo mode without API key: postgres-ai mon local-install --demo`);
30649
32083
  node_name: ${instanceName}
30650
32084
  sink_type: ~sink_type~
30651
32085
  `;
30652
- fs5.appendFileSync(instancesPath, body, "utf8");
32086
+ fs6.appendFileSync(instancesPath2, body, "utf8");
30653
32087
  console.log(`\u2713 Monitoring target '${instanceName}' added
30654
32088
  `);
30655
32089
  console.log("Testing connection to the added instance...");
30656
- try {
30657
- const client = new Client({ connectionString: connStr });
30658
- await client.connect();
30659
- const result = await client.query("select version();");
30660
- console.log("\u2713 Connection successful");
30661
- console.log(`${result.rows[0].version}
32090
+ {
32091
+ let testClient = null;
32092
+ try {
32093
+ testClient = new Client({ connectionString: connStr, connectionTimeoutMillis: 1e4 });
32094
+ await testClient.connect();
32095
+ const result = await testClient.query("select version();");
32096
+ console.log("\u2713 Connection successful");
32097
+ console.log(`${result.rows[0].version}
30662
32098
  `);
30663
- await client.end();
30664
- } catch (error2) {
30665
- const message = error2 instanceof Error ? error2.message : String(error2);
30666
- console.error(`\u2717 Connection failed: ${message}
32099
+ } catch (error2) {
32100
+ const message = error2 instanceof Error ? error2.message : String(error2);
32101
+ console.error(`\u2717 Connection failed: ${message}
30667
32102
  `);
32103
+ } finally {
32104
+ if (testClient)
32105
+ await testClient.end();
32106
+ }
30668
32107
  }
30669
32108
  } else if (opts.yes) {
30670
32109
  console.log("Auto-yes mode: no database URL provided, skipping database setup");
30671
- console.log("\u26A0 No PostgreSQL instance added");
32110
+ console.error("\u26A0 No PostgreSQL instance added");
30672
32111
  console.log(`You can add one later with: postgres-ai mon targets add
30673
32112
  `);
30674
32113
  } else {
@@ -30686,7 +32125,7 @@ You can provide either:`);
30686
32125
  const m = connStr.match(/^postgresql:\/\/([^:]+):([^@]+)@([^:\/]+)(?::(\d+))?\/(.+)$/);
30687
32126
  if (!m) {
30688
32127
  console.error("\u2717 Invalid connection string format");
30689
- console.log(`\u26A0 Continuing without adding instance
32128
+ console.error(`\u26A0 Continuing without adding instance
30690
32129
  `);
30691
32130
  } else {
30692
32131
  const host = m[3];
@@ -30704,36 +32143,59 @@ You can provide either:`);
30704
32143
  node_name: ${instanceName}
30705
32144
  sink_type: ~sink_type~
30706
32145
  `;
30707
- fs5.appendFileSync(instancesPath, body, "utf8");
32146
+ fs6.appendFileSync(instancesPath2, body, "utf8");
30708
32147
  console.log(`\u2713 Monitoring target '${instanceName}' added
30709
32148
  `);
30710
32149
  console.log("Testing connection to the added instance...");
30711
- try {
30712
- const client = new Client({ connectionString: connStr });
30713
- await client.connect();
30714
- const result = await client.query("select version();");
30715
- console.log("\u2713 Connection successful");
30716
- console.log(`${result.rows[0].version}
32150
+ {
32151
+ let testClient = null;
32152
+ try {
32153
+ testClient = new Client({ connectionString: connStr, connectionTimeoutMillis: 1e4 });
32154
+ await testClient.connect();
32155
+ const result = await testClient.query("select version();");
32156
+ console.log("\u2713 Connection successful");
32157
+ console.log(`${result.rows[0].version}
30717
32158
  `);
30718
- await client.end();
30719
- } catch (error2) {
30720
- const message = error2 instanceof Error ? error2.message : String(error2);
30721
- console.error(`\u2717 Connection failed: ${message}
32159
+ } catch (error2) {
32160
+ const message = error2 instanceof Error ? error2.message : String(error2);
32161
+ console.error(`\u2717 Connection failed: ${message}
30722
32162
  `);
32163
+ } finally {
32164
+ if (testClient)
32165
+ await testClient.end();
32166
+ }
30723
32167
  }
30724
32168
  }
30725
32169
  } else {
30726
- console.log(`\u26A0 No PostgreSQL instance added - you can add one later with: postgres-ai mon targets add
32170
+ console.error(`\u26A0 No PostgreSQL instance added - you can add one later with: postgres-ai mon targets add
30727
32171
  `);
30728
32172
  }
30729
32173
  } else {
30730
- console.log(`\u26A0 No PostgreSQL instance added - you can add one later with: postgres-ai mon targets add
32174
+ console.error(`\u26A0 No PostgreSQL instance added - you can add one later with: postgres-ai mon targets add
30731
32175
  `);
30732
32176
  }
30733
32177
  }
30734
32178
  } else {
30735
- console.log(`Step 2: Demo mode enabled - using included demo PostgreSQL database
32179
+ console.log("Step 2: Demo mode enabled - using included demo PostgreSQL database");
32180
+ const currentDir = path6.dirname(fileURLToPath2(import.meta.url));
32181
+ const demoCandidates = [
32182
+ path6.resolve(currentDir, "..", "..", "instances.demo.yml"),
32183
+ path6.resolve(currentDir, "..", "..", "..", "instances.demo.yml")
32184
+ ];
32185
+ const demoSrc = demoCandidates.find((p) => fs6.existsSync(p));
32186
+ if (demoSrc) {
32187
+ if (fs6.existsSync(instancesPath) && fs6.lstatSync(instancesPath).isDirectory()) {
32188
+ fs6.rmSync(instancesPath, { recursive: true, force: true });
32189
+ }
32190
+ fs6.copyFileSync(demoSrc, instancesPath);
32191
+ console.log(`\u2713 Demo monitoring target configured
30736
32192
  `);
32193
+ } else {
32194
+ console.error(`Error: instances.demo.yml not found \u2014 cannot configure demo target.
32195
+ Searched: ${demoCandidates.join(", ")}
32196
+ `);
32197
+ process.exit(1);
32198
+ }
30737
32199
  }
30738
32200
  console.log(opts.demo ? "Step 3: Updating configuration..." : "Step 3: Updating configuration...");
30739
32201
  const code1 = await runCompose(["run", "--rm", "sources-generator"]);
@@ -30744,13 +32206,15 @@ You can provide either:`);
30744
32206
  console.log(`\u2713 Configuration updated
30745
32207
  `);
30746
32208
  console.log(opts.demo ? "Step 4: Configuring Grafana security..." : "Step 4: Configuring Grafana security...");
30747
- const cfgPath = path5.resolve(projectDir, ".pgwatch-config");
32209
+ const cfgPath = path6.resolve(projectDir, ".pgwatch-config");
30748
32210
  let grafanaPassword = "";
32211
+ let vmAuthUsername = "";
32212
+ let vmAuthPassword = "";
30749
32213
  try {
30750
- if (fs5.existsSync(cfgPath)) {
30751
- const stats = fs5.statSync(cfgPath);
32214
+ if (fs6.existsSync(cfgPath)) {
32215
+ const stats = fs6.statSync(cfgPath);
30752
32216
  if (!stats.isDirectory()) {
30753
- const content = fs5.readFileSync(cfgPath, "utf8");
32217
+ const content = fs6.readFileSync(cfgPath, "utf8");
30754
32218
  const match = content.match(/^grafana_password=([^\r\n]+)/m);
30755
32219
  if (match) {
30756
32220
  grafanaPassword = match[1].trim();
@@ -30759,30 +32223,67 @@ You can provide either:`);
30759
32223
  }
30760
32224
  if (!grafanaPassword) {
30761
32225
  console.log("Generating secure Grafana password...");
30762
- const { stdout: password } = await execPromise(`openssl rand -base64 12 | tr -d '
30763
- '`);
30764
- grafanaPassword = password.trim();
32226
+ const { stdout: password } = await execFilePromise("openssl", ["rand", "-base64", "12"]);
32227
+ grafanaPassword = password.trim().replace(/\n/g, "");
30765
32228
  let configContent = "";
30766
- if (fs5.existsSync(cfgPath)) {
30767
- const stats = fs5.statSync(cfgPath);
32229
+ if (fs6.existsSync(cfgPath)) {
32230
+ const stats = fs6.statSync(cfgPath);
30768
32231
  if (!stats.isDirectory()) {
30769
- configContent = fs5.readFileSync(cfgPath, "utf8");
32232
+ configContent = fs6.readFileSync(cfgPath, "utf8");
30770
32233
  }
30771
32234
  }
30772
32235
  const lines = configContent.split(/\r?\n/).filter((l) => !/^grafana_password=/.test(l));
30773
32236
  lines.push(`grafana_password=${grafanaPassword}`);
30774
- fs5.writeFileSync(cfgPath, lines.filter(Boolean).join(`
32237
+ fs6.writeFileSync(cfgPath, lines.filter(Boolean).join(`
30775
32238
  `) + `
30776
32239
  `, "utf8");
30777
32240
  }
30778
32241
  console.log(`\u2713 Grafana password configured
30779
32242
  `);
30780
32243
  } catch (error2) {
30781
- console.log("\u26A0 Could not generate Grafana password automatically");
32244
+ console.error("\u26A0 Could not generate Grafana password automatically");
30782
32245
  console.log(`Using default password: demo
30783
32246
  `);
30784
32247
  grafanaPassword = "demo";
30785
32248
  }
32249
+ try {
32250
+ const envFile2 = path6.resolve(projectDir, ".env");
32251
+ if (fs6.existsSync(envFile2)) {
32252
+ const envContent = fs6.readFileSync(envFile2, "utf8");
32253
+ const userMatch = envContent.match(/^VM_AUTH_USERNAME=([^\r\n]+)/m);
32254
+ const passMatch = envContent.match(/^VM_AUTH_PASSWORD=([^\r\n]+)/m);
32255
+ if (userMatch)
32256
+ vmAuthUsername = stripMatchingQuotes(userMatch[1]);
32257
+ if (passMatch)
32258
+ vmAuthPassword = stripMatchingQuotes(passMatch[1]);
32259
+ }
32260
+ if (!vmAuthUsername || !vmAuthPassword) {
32261
+ console.log("Generating VictoriaMetrics auth credentials...");
32262
+ vmAuthUsername = vmAuthUsername || "vmauth";
32263
+ if (!vmAuthPassword) {
32264
+ const { stdout: vmPass } = await execFilePromise("openssl", ["rand", "-base64", "12"]);
32265
+ vmAuthPassword = vmPass.trim().replace(/\n/g, "");
32266
+ }
32267
+ let envContent = "";
32268
+ if (fs6.existsSync(envFile2)) {
32269
+ envContent = fs6.readFileSync(envFile2, "utf8");
32270
+ }
32271
+ const envLines2 = envContent.split(/\r?\n/).filter((l) => !/^VM_AUTH_USERNAME=/.test(l) && !/^VM_AUTH_PASSWORD=/.test(l)).filter((l, i2, arr) => !(i2 === arr.length - 1 && l === ""));
32272
+ envLines2.push(`VM_AUTH_USERNAME=${vmAuthUsername}`);
32273
+ envLines2.push(`VM_AUTH_PASSWORD=${vmAuthPassword}`);
32274
+ fs6.writeFileSync(envFile2, envLines2.join(`
32275
+ `) + `
32276
+ `, { encoding: "utf8", mode: 384 });
32277
+ }
32278
+ console.log(`\u2713 VictoriaMetrics auth configured
32279
+ `);
32280
+ } catch (error2) {
32281
+ console.error("\u26A0 Could not generate VictoriaMetrics auth credentials automatically");
32282
+ if (process.env.DEBUG) {
32283
+ console.warn(` ${error2 instanceof Error ? error2.message : String(error2)}`);
32284
+ }
32285
+ }
32286
+ await runCompose(["rm", "-f", "-s", "config-init"]);
30786
32287
  console.log("Step 5: Starting monitoring services...");
30787
32288
  const code2 = await runCompose(["up", "-d", "--force-recreate"], grafanaPassword);
30788
32289
  if (code2 !== 0) {
@@ -30830,6 +32331,9 @@ You can provide either:`);
30830
32331
  console.log("\uD83D\uDE80 MAIN ACCESS POINT - Start here:");
30831
32332
  console.log(" Grafana Dashboard: http://localhost:3000");
30832
32333
  console.log(` Login: monitor / ${grafanaPassword}`);
32334
+ if (vmAuthUsername && vmAuthPassword) {
32335
+ console.log(` VictoriaMetrics Auth: ${vmAuthUsername} / ${vmAuthPassword}`);
32336
+ }
30833
32337
  console.log(`\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
30834
32338
  `);
30835
32339
  });
@@ -30873,7 +32377,7 @@ mon.command("stop").description("stop monitoring services").action(async () => {
30873
32377
  let code = await runCompose(["down", "--remove-orphans"]);
30874
32378
  if (code !== 0) {
30875
32379
  await removeOrphanedContainers();
30876
- await new Promise((resolve6) => setTimeout(resolve6, NETWORK_CLEANUP_DELAY_MS));
32380
+ await new Promise((resolve7) => setTimeout(resolve7, NETWORK_CLEANUP_DELAY_MS));
30877
32381
  code = await runCompose(["down", "--remove-orphans"]);
30878
32382
  }
30879
32383
  if (code !== 0) {
@@ -30928,7 +32432,7 @@ mon.command("health").description("health check for monitoring services").option
30928
32432
  if (attempt > 1) {
30929
32433
  console.log(`Retrying (attempt ${attempt}/${maxAttempts})...
30930
32434
  `);
30931
- await new Promise((resolve6) => setTimeout(resolve6, 5000));
32435
+ await new Promise((resolve7) => setTimeout(resolve7, 5000));
30932
32436
  }
30933
32437
  allHealthy = true;
30934
32438
  for (const service of services) {
@@ -30976,11 +32480,11 @@ mon.command("config").description("show monitoring services configuration").acti
30976
32480
  console.log(`Project Directory: ${projectDir}`);
30977
32481
  console.log(`Docker Compose File: ${composeFile}`);
30978
32482
  console.log(`Instances File: ${instancesFile}`);
30979
- if (fs5.existsSync(instancesFile)) {
32483
+ if (fs6.existsSync(instancesFile) && !fs6.lstatSync(instancesFile).isDirectory()) {
30980
32484
  console.log(`
30981
32485
  Instances configuration:
30982
32486
  `);
30983
- const text = fs5.readFileSync(instancesFile, "utf8");
32487
+ const text = fs6.readFileSync(instancesFile, "utf8");
30984
32488
  process.stdout.write(text);
30985
32489
  if (!/\n$/.test(text))
30986
32490
  console.log();
@@ -30995,19 +32499,19 @@ mon.command("update").description("update monitoring stack").action(async () =>
30995
32499
  console.log(`Updating PostgresAI monitoring stack...
30996
32500
  `);
30997
32501
  try {
30998
- const gitDir = path5.resolve(process.cwd(), ".git");
30999
- if (!fs5.existsSync(gitDir)) {
32502
+ const gitDir = path6.resolve(process.cwd(), ".git");
32503
+ if (!fs6.existsSync(gitDir)) {
31000
32504
  console.error("Not a git repository. Cannot update.");
31001
32505
  process.exitCode = 1;
31002
32506
  return;
31003
32507
  }
31004
32508
  console.log("Fetching latest changes...");
31005
- await execPromise("git fetch origin");
31006
- const { stdout: branch } = await execPromise("git rev-parse --abbrev-ref HEAD");
32509
+ await execFilePromise("git", ["fetch", "origin"]);
32510
+ const { stdout: branch } = await execFilePromise("git", ["rev-parse", "--abbrev-ref", "HEAD"]);
31007
32511
  const currentBranch = branch.trim();
31008
32512
  console.log(`Current branch: ${currentBranch}`);
31009
32513
  console.log("Pulling latest changes...");
31010
- const { stdout: pullOut } = await execPromise("git pull origin " + currentBranch);
32514
+ const { stdout: pullOut } = await execFilePromise("git", ["pull", "origin", currentBranch]);
31011
32515
  console.log(pullOut);
31012
32516
  console.log(`
31013
32517
  Updating Docker images...`);
@@ -31092,7 +32596,7 @@ mon.command("clean").description("cleanup monitoring services artifacts (stops s
31092
32596
  if (downCode === 0) {
31093
32597
  console.log("\u2713 Monitoring services stopped and removed");
31094
32598
  } else {
31095
- console.log("\u26A0 Could not stop services (may not be running)");
32599
+ console.error("\u26A0 Could not stop services (may not be running)");
31096
32600
  }
31097
32601
  await removeOrphanedContainers();
31098
32602
  console.log("\u2713 Removed orphaned containers");
@@ -31151,13 +32655,13 @@ mon.command("check").description("monitoring services system readiness check").a
31151
32655
  var targets = mon.command("targets").description("manage databases to monitor");
31152
32656
  targets.command("list").description("list monitoring target databases").action(async () => {
31153
32657
  const { instancesFile: instancesPath, projectDir } = await resolveOrInitPaths();
31154
- if (!fs5.existsSync(instancesPath)) {
32658
+ if (!fs6.existsSync(instancesPath) || fs6.lstatSync(instancesPath).isDirectory()) {
31155
32659
  console.error(`instances.yml not found in ${projectDir}`);
31156
32660
  process.exitCode = 1;
31157
32661
  return;
31158
32662
  }
31159
32663
  try {
31160
- const content = fs5.readFileSync(instancesPath, "utf8");
32664
+ const content = fs6.readFileSync(instancesPath, "utf8");
31161
32665
  const instances = load(content);
31162
32666
  if (!instances || !Array.isArray(instances) || instances.length === 0) {
31163
32667
  console.log("No monitoring targets configured");
@@ -31206,8 +32710,8 @@ targets.command("add [connStr] [name]").description("add monitoring target datab
31206
32710
  const db = m[5];
31207
32711
  const instanceName = name && name.trim() ? name.trim() : `${host}-${db}`.replace(/[^a-zA-Z0-9-]/g, "-");
31208
32712
  try {
31209
- if (fs5.existsSync(file)) {
31210
- const content2 = fs5.readFileSync(file, "utf8");
32713
+ if (fs6.existsSync(file) && !fs6.lstatSync(file).isDirectory()) {
32714
+ const content2 = fs6.readFileSync(file, "utf8");
31211
32715
  const instances = load(content2) || [];
31212
32716
  if (Array.isArray(instances)) {
31213
32717
  const exists = instances.some((inst) => inst.name === instanceName);
@@ -31219,13 +32723,18 @@ targets.command("add [connStr] [name]").description("add monitoring target datab
31219
32723
  }
31220
32724
  }
31221
32725
  } catch (err) {
31222
- const content2 = fs5.existsSync(file) ? fs5.readFileSync(file, "utf8") : "";
31223
- if (new RegExp(`^- name: ${instanceName}$`, "m").test(content2)) {
32726
+ const isFile = fs6.existsSync(file) && !fs6.lstatSync(file).isDirectory();
32727
+ const content2 = isFile ? fs6.readFileSync(file, "utf8") : "";
32728
+ const escapedName = instanceName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
32729
+ if (new RegExp(`^- name: ${escapedName}$`, "m").test(content2)) {
31224
32730
  console.error(`Monitoring target '${instanceName}' already exists`);
31225
32731
  process.exitCode = 1;
31226
32732
  return;
31227
32733
  }
31228
32734
  }
32735
+ if (fs6.existsSync(file) && fs6.lstatSync(file).isDirectory()) {
32736
+ fs6.rmSync(file, { recursive: true, force: true });
32737
+ }
31229
32738
  const body = `- name: ${instanceName}
31230
32739
  conn_str: ${connStr}
31231
32740
  preset_metrics: full
@@ -31238,20 +32747,20 @@ targets.command("add [connStr] [name]").description("add monitoring target datab
31238
32747
  node_name: ${instanceName}
31239
32748
  sink_type: ~sink_type~
31240
32749
  `;
31241
- const content = fs5.existsSync(file) ? fs5.readFileSync(file, "utf8") : "";
31242
- fs5.appendFileSync(file, (content && !/\n$/.test(content) ? `
32750
+ const content = fs6.existsSync(file) ? fs6.readFileSync(file, "utf8") : "";
32751
+ fs6.appendFileSync(file, (content && !/\n$/.test(content) ? `
31243
32752
  ` : "") + body, "utf8");
31244
32753
  console.log(`Monitoring target '${instanceName}' added`);
31245
32754
  });
31246
32755
  targets.command("remove <name>").description("remove monitoring target database").action(async (name) => {
31247
32756
  const { instancesFile: file } = await resolveOrInitPaths();
31248
- if (!fs5.existsSync(file)) {
32757
+ if (!fs6.existsSync(file) || fs6.lstatSync(file).isDirectory()) {
31249
32758
  console.error("instances.yml not found");
31250
32759
  process.exitCode = 1;
31251
32760
  return;
31252
32761
  }
31253
32762
  try {
31254
- const content = fs5.readFileSync(file, "utf8");
32763
+ const content = fs6.readFileSync(file, "utf8");
31255
32764
  const instances = load(content);
31256
32765
  if (!instances || !Array.isArray(instances)) {
31257
32766
  console.error("Invalid instances.yml format");
@@ -31264,7 +32773,7 @@ targets.command("remove <name>").description("remove monitoring target database"
31264
32773
  process.exitCode = 1;
31265
32774
  return;
31266
32775
  }
31267
- fs5.writeFileSync(file, dump(filtered), "utf8");
32776
+ fs6.writeFileSync(file, dump(filtered), "utf8");
31268
32777
  console.log(`Monitoring target '${name}' removed`);
31269
32778
  } catch (err) {
31270
32779
  const message = err instanceof Error ? err.message : String(err);
@@ -31274,13 +32783,13 @@ targets.command("remove <name>").description("remove monitoring target database"
31274
32783
  });
31275
32784
  targets.command("test <name>").description("test monitoring target database connectivity").action(async (name) => {
31276
32785
  const { instancesFile: instancesPath } = await resolveOrInitPaths();
31277
- if (!fs5.existsSync(instancesPath)) {
32786
+ if (!fs6.existsSync(instancesPath) || fs6.lstatSync(instancesPath).isDirectory()) {
31278
32787
  console.error("instances.yml not found");
31279
32788
  process.exitCode = 1;
31280
32789
  return;
31281
32790
  }
31282
32791
  try {
31283
- const content = fs5.readFileSync(instancesPath, "utf8");
32792
+ const content = fs6.readFileSync(instancesPath, "utf8");
31284
32793
  const instances = load(content);
31285
32794
  if (!instances || !Array.isArray(instances)) {
31286
32795
  console.error("Invalid instances.yml format");
@@ -31299,7 +32808,7 @@ targets.command("test <name>").description("test monitoring target database conn
31299
32808
  return;
31300
32809
  }
31301
32810
  console.log(`Testing connection to monitoring target '${name}'...`);
31302
- const client = new Client({ connectionString: instance.conn_str });
32811
+ const client = new Client({ connectionString: instance.conn_str, connectionTimeoutMillis: 1e4 });
31303
32812
  try {
31304
32813
  await client.connect();
31305
32814
  const result = await client.query("select version();");
@@ -31341,8 +32850,8 @@ auth.command("login", { isDefault: true }).description("authenticate via browser
31341
32850
  const cfg = readConfig();
31342
32851
  const { apiBaseUrl, uiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
31343
32852
  if (opts.debug) {
31344
- console.log(`Debug: Resolved API base URL: ${apiBaseUrl}`);
31345
- console.log(`Debug: Resolved UI base URL: ${uiBaseUrl}`);
32853
+ console.error(`Debug: Resolved API base URL: ${apiBaseUrl}`);
32854
+ console.error(`Debug: Resolved UI base URL: ${uiBaseUrl}`);
31346
32855
  }
31347
32856
  try {
31348
32857
  console.log("Starting local callback server...");
@@ -31361,8 +32870,8 @@ auth.command("login", { isDefault: true }).description("authenticate via browser
31361
32870
  });
31362
32871
  const initUrl = new URL(`${apiBaseUrl}/rpc/oauth_init`);
31363
32872
  if (opts.debug) {
31364
- console.log(`Debug: Trying to POST to: ${initUrl.toString()}`);
31365
- console.log(`Debug: Request data: ${initData}`);
32873
+ console.error(`Debug: Trying to POST to: ${initUrl.toString()}`);
32874
+ console.error(`Debug: Request data: ${initData}`);
31366
32875
  }
31367
32876
  let initResponse;
31368
32877
  try {
@@ -31400,7 +32909,7 @@ Please verify the --api-base-url parameter.`);
31400
32909
  }
31401
32910
  const authUrl = `${uiBaseUrl}/cli/auth?state=${encodeURIComponent(params.state)}&code_challenge=${encodeURIComponent(params.codeChallenge)}&code_challenge_method=S256&redirect_uri=${encodeURIComponent(redirectUri)}&api_url=${encodeURIComponent(apiBaseUrl)}`;
31402
32911
  if (opts.debug) {
31403
- console.log(`Debug: Auth URL: ${authUrl}`);
32912
+ console.error(`Debug: Auth URL: ${authUrl}`);
31404
32913
  }
31405
32914
  console.log(`
31406
32915
  Opening browser for authentication...`);
@@ -31533,15 +33042,15 @@ To authenticate, run: pgai auth`);
31533
33042
  });
31534
33043
  auth.command("remove-key").description("remove API key").action(async () => {
31535
33044
  const newConfigPath = getConfigPath();
31536
- const hasNewConfig = fs5.existsSync(newConfigPath);
33045
+ const hasNewConfig = fs6.existsSync(newConfigPath);
31537
33046
  let legacyPath;
31538
33047
  try {
31539
33048
  const { projectDir } = await resolveOrInitPaths();
31540
- legacyPath = path5.resolve(projectDir, ".pgwatch-config");
33049
+ legacyPath = path6.resolve(projectDir, ".pgwatch-config");
31541
33050
  } catch {
31542
- legacyPath = path5.resolve(process.cwd(), ".pgwatch-config");
33051
+ legacyPath = path6.resolve(process.cwd(), ".pgwatch-config");
31543
33052
  }
31544
- const hasLegacyConfig = fs5.existsSync(legacyPath) && fs5.statSync(legacyPath).isFile();
33053
+ const hasLegacyConfig = fs6.existsSync(legacyPath) && fs6.statSync(legacyPath).isFile();
31545
33054
  if (!hasNewConfig && !hasLegacyConfig) {
31546
33055
  console.log("No API key configured");
31547
33056
  return;
@@ -31551,11 +33060,11 @@ auth.command("remove-key").description("remove API key").action(async () => {
31551
33060
  }
31552
33061
  if (hasLegacyConfig) {
31553
33062
  try {
31554
- const content = fs5.readFileSync(legacyPath, "utf8");
33063
+ const content = fs6.readFileSync(legacyPath, "utf8");
31555
33064
  const filtered = content.split(/\r?\n/).filter((l) => !/^api_key=/.test(l)).join(`
31556
33065
  `).replace(/\n+$/g, `
31557
33066
  `);
31558
- fs5.writeFileSync(legacyPath, filtered, "utf8");
33067
+ fs6.writeFileSync(legacyPath, filtered, "utf8");
31559
33068
  } catch (err) {
31560
33069
  console.warn(`Warning: Could not update legacy config: ${err instanceof Error ? err.message : String(err)}`);
31561
33070
  }
@@ -31566,28 +33075,27 @@ To authenticate again, run: pgai auth`);
31566
33075
  });
31567
33076
  mon.command("generate-grafana-password").description("generate Grafana password for monitoring services").action(async () => {
31568
33077
  const { projectDir } = await resolveOrInitPaths();
31569
- const cfgPath = path5.resolve(projectDir, ".pgwatch-config");
33078
+ const cfgPath = path6.resolve(projectDir, ".pgwatch-config");
31570
33079
  try {
31571
- const { stdout: password } = await execPromise(`openssl rand -base64 12 | tr -d '
31572
- '`);
31573
- const newPassword = password.trim();
33080
+ const { stdout: password } = await execFilePromise("openssl", ["rand", "-base64", "12"]);
33081
+ const newPassword = password.trim().replace(/\n/g, "");
31574
33082
  if (!newPassword) {
31575
33083
  console.error("Failed to generate password");
31576
33084
  process.exitCode = 1;
31577
33085
  return;
31578
33086
  }
31579
33087
  let configContent = "";
31580
- if (fs5.existsSync(cfgPath)) {
31581
- const stats = fs5.statSync(cfgPath);
33088
+ if (fs6.existsSync(cfgPath)) {
33089
+ const stats = fs6.statSync(cfgPath);
31582
33090
  if (stats.isDirectory()) {
31583
33091
  console.error(".pgwatch-config is a directory, expected a file. Skipping read.");
31584
33092
  } else {
31585
- configContent = fs5.readFileSync(cfgPath, "utf8");
33093
+ configContent = fs6.readFileSync(cfgPath, "utf8");
31586
33094
  }
31587
33095
  }
31588
33096
  const lines = configContent.split(/\r?\n/).filter((l) => !/^grafana_password=/.test(l));
31589
33097
  lines.push(`grafana_password=${newPassword}`);
31590
- fs5.writeFileSync(cfgPath, lines.filter(Boolean).join(`
33098
+ fs6.writeFileSync(cfgPath, lines.filter(Boolean).join(`
31591
33099
  `) + `
31592
33100
  `, "utf8");
31593
33101
  console.log("\u2713 New Grafana password generated and saved");
@@ -31609,19 +33117,19 @@ Note: This command requires 'openssl' to be installed`);
31609
33117
  });
31610
33118
  mon.command("show-grafana-credentials").description("show Grafana credentials for monitoring services").action(async () => {
31611
33119
  const { projectDir } = await resolveOrInitPaths();
31612
- const cfgPath = path5.resolve(projectDir, ".pgwatch-config");
31613
- if (!fs5.existsSync(cfgPath)) {
33120
+ const cfgPath = path6.resolve(projectDir, ".pgwatch-config");
33121
+ if (!fs6.existsSync(cfgPath)) {
31614
33122
  console.error("Configuration file not found. Run 'postgres-ai mon local-install' first.");
31615
33123
  process.exitCode = 1;
31616
33124
  return;
31617
33125
  }
31618
- const stats = fs5.statSync(cfgPath);
33126
+ const stats = fs6.statSync(cfgPath);
31619
33127
  if (stats.isDirectory()) {
31620
33128
  console.error(".pgwatch-config is a directory, expected a file. Cannot read credentials.");
31621
33129
  process.exitCode = 1;
31622
33130
  return;
31623
33131
  }
31624
- const content = fs5.readFileSync(cfgPath, "utf8");
33132
+ const content = fs6.readFileSync(cfgPath, "utf8");
31625
33133
  const lines = content.split(/\r?\n/);
31626
33134
  let password = "";
31627
33135
  for (const line of lines) {
@@ -31641,6 +33149,18 @@ Grafana credentials:`);
31641
33149
  console.log(" URL: http://localhost:3000");
31642
33150
  console.log(" Username: monitor");
31643
33151
  console.log(` Password: ${password}`);
33152
+ const envFile = path6.resolve(projectDir, ".env");
33153
+ if (fs6.existsSync(envFile)) {
33154
+ const envContent = fs6.readFileSync(envFile, "utf8");
33155
+ const vmUser = envContent.match(/^VM_AUTH_USERNAME=([^\r\n]+)/m);
33156
+ const vmPass = envContent.match(/^VM_AUTH_PASSWORD=([^\r\n]+)/m);
33157
+ if (vmUser && vmPass) {
33158
+ console.log(`
33159
+ VictoriaMetrics credentials:`);
33160
+ console.log(` Username: ${stripMatchingQuotes(vmUser[1])}`);
33161
+ console.log(` Password: ${stripMatchingQuotes(vmPass[1])}`);
33162
+ }
33163
+ }
31644
33164
  console.log("");
31645
33165
  });
31646
33166
  function interpretEscapes2(str2) {
@@ -31726,11 +33246,11 @@ issues.command("view <issueId>").description("view issue details and comments").
31726
33246
  });
31727
33247
  issues.command("post-comment <issueId> <content>").description("post a new comment to an issue").option("--parent <uuid>", "parent comment id").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (issueId, content, opts) => {
31728
33248
  if (opts.debug) {
31729
- console.log(`Debug: Original content: ${JSON.stringify(content)}`);
33249
+ console.error(`Debug: Original content: ${JSON.stringify(content)}`);
31730
33250
  }
31731
33251
  content = interpretEscapes2(content);
31732
33252
  if (opts.debug) {
31733
- console.log(`Debug: Interpreted content: ${JSON.stringify(content)}`);
33253
+ console.error(`Debug: Interpreted content: ${JSON.stringify(content)}`);
31734
33254
  }
31735
33255
  const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Posting comment...");
31736
33256
  try {
@@ -31876,11 +33396,11 @@ issues.command("update <issueId>").description("update an existing issue (title/
31876
33396
  });
31877
33397
  issues.command("update-comment <commentId> <content>").description("update an existing issue comment").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (commentId, content, opts) => {
31878
33398
  if (opts.debug) {
31879
- console.log(`Debug: Original content: ${JSON.stringify(content)}`);
33399
+ console.error(`Debug: Original content: ${JSON.stringify(content)}`);
31880
33400
  }
31881
33401
  content = interpretEscapes2(content);
31882
33402
  if (opts.debug) {
31883
- console.log(`Debug: Interpreted content: ${JSON.stringify(content)}`);
33403
+ console.error(`Debug: Interpreted content: ${JSON.stringify(content)}`);
31884
33404
  }
31885
33405
  const rootOpts = program2.opts();
31886
33406
  const cfg = readConfig();
@@ -31909,6 +33429,74 @@ issues.command("update-comment <commentId> <content>").description("update an ex
31909
33429
  process.exitCode = 1;
31910
33430
  }
31911
33431
  });
33432
+ var issueFiles = issues.command("files").description("upload and download files for issues");
33433
+ issueFiles.command("upload <path>").description("upload a file to storage and get a markdown link").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (filePath, opts) => {
33434
+ const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Uploading file...");
33435
+ try {
33436
+ const rootOpts = program2.opts();
33437
+ const cfg = readConfig();
33438
+ const { apiKey } = getConfig(rootOpts);
33439
+ if (!apiKey) {
33440
+ spinner.stop();
33441
+ console.error("API key is required. Run 'pgai auth' first or set --api-key.");
33442
+ process.exitCode = 1;
33443
+ return;
33444
+ }
33445
+ const { storageBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
33446
+ const result = await uploadFile({
33447
+ apiKey,
33448
+ storageBaseUrl,
33449
+ filePath,
33450
+ debug: !!opts.debug
33451
+ });
33452
+ spinner.stop();
33453
+ if (opts.json) {
33454
+ printResult(result, true);
33455
+ } else {
33456
+ const md = buildMarkdownLink(result.url, storageBaseUrl, result.metadata.originalName);
33457
+ const displayUrl = result.url.startsWith("/") ? `${storageBaseUrl}${result.url}` : `${storageBaseUrl}/${result.url}`;
33458
+ console.log(`URL: ${displayUrl}`);
33459
+ console.log(`File: ${result.metadata.originalName}`);
33460
+ console.log(`Size: ${result.metadata.size} bytes`);
33461
+ console.log(`Type: ${result.metadata.mimeType}`);
33462
+ console.log(`Markdown: ${md}`);
33463
+ }
33464
+ } catch (err) {
33465
+ spinner.stop();
33466
+ const message = err instanceof Error ? err.message : String(err);
33467
+ console.error(message);
33468
+ process.exitCode = 1;
33469
+ }
33470
+ });
33471
+ issueFiles.command("download <url>").description("download a file from storage").option("-o, --output <path>", "output file path (default: derive from URL)").option("--debug", "enable debug output").action(async (fileUrl, opts) => {
33472
+ const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Downloading file...");
33473
+ try {
33474
+ const rootOpts = program2.opts();
33475
+ const cfg = readConfig();
33476
+ const { apiKey } = getConfig(rootOpts);
33477
+ if (!apiKey) {
33478
+ spinner.stop();
33479
+ console.error("API key is required. Run 'pgai auth' first or set --api-key.");
33480
+ process.exitCode = 1;
33481
+ return;
33482
+ }
33483
+ const { storageBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
33484
+ const result = await downloadFile({
33485
+ apiKey,
33486
+ storageBaseUrl,
33487
+ fileUrl,
33488
+ outputPath: opts.output,
33489
+ debug: !!opts.debug
33490
+ });
33491
+ spinner.stop();
33492
+ console.log(`Saved: ${result.savedTo}`);
33493
+ } catch (err) {
33494
+ spinner.stop();
33495
+ const message = err instanceof Error ? err.message : String(err);
33496
+ console.error(message);
33497
+ process.exitCode = 1;
33498
+ }
33499
+ });
31912
33500
  issues.command("action-items <issueId>").description("list action items for an issue").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (issueId, opts) => {
31913
33501
  const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Fetching action items...");
31914
33502
  try {
@@ -32081,6 +33669,196 @@ issues.command("update-action-item <actionItemId>").description("update an actio
32081
33669
  process.exitCode = 1;
32082
33670
  }
32083
33671
  });
33672
+ var reports = program2.command("reports").description("checkup reports management");
33673
+ reports.command("list").description("list checkup reports").option("--project-id <id>", "filter by project id", (v) => parseInt(v, 10)).addOption(new Option("--status <status>", "filter by status (e.g., completed)").hideHelp()).option("--limit <n>", "max number of reports to return (default: 20, max: 100)", (v) => {
33674
+ const n = parseInt(v, 10);
33675
+ return Number.isNaN(n) ? 20 : Math.max(1, Math.min(n, 100));
33676
+ }).option("--before <date>", "show reports created before this date (YYYY-MM-DD, DD.MM.YYYY, etc.)").option("--all", "fetch all reports (paginated automatically)").addOption(new Option("--debug", "enable debug output").hideHelp()).option("--json", "output raw JSON").action(async (opts) => {
33677
+ const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Fetching reports...");
33678
+ try {
33679
+ const rootOpts = program2.opts();
33680
+ const cfg = readConfig();
33681
+ const { apiKey } = getConfig(rootOpts);
33682
+ if (!apiKey) {
33683
+ spinner.stop();
33684
+ console.error("API key is required. Run 'pgai auth' first or set --api-key.");
33685
+ process.exitCode = 1;
33686
+ return;
33687
+ }
33688
+ if (opts.all && opts.before) {
33689
+ spinner.stop();
33690
+ console.error("--all and --before cannot be used together");
33691
+ process.exitCode = 1;
33692
+ return;
33693
+ }
33694
+ const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
33695
+ let result;
33696
+ if (opts.all) {
33697
+ result = await fetchAllReports2({
33698
+ apiKey,
33699
+ apiBaseUrl,
33700
+ projectId: opts.projectId,
33701
+ status: opts.status,
33702
+ limit: opts.limit,
33703
+ debug: !!opts.debug
33704
+ });
33705
+ } else {
33706
+ result = await fetchReports2({
33707
+ apiKey,
33708
+ apiBaseUrl,
33709
+ projectId: opts.projectId,
33710
+ status: opts.status,
33711
+ limit: opts.limit,
33712
+ beforeDate: opts.before ? parseFlexibleDate2(opts.before) : undefined,
33713
+ debug: !!opts.debug
33714
+ });
33715
+ }
33716
+ spinner.stop();
33717
+ printResult(result, opts.json);
33718
+ } catch (err) {
33719
+ spinner.stop();
33720
+ const message = err instanceof Error ? err.message : String(err);
33721
+ console.error(message);
33722
+ process.exitCode = 1;
33723
+ }
33724
+ });
33725
+ reports.command("files [reportId]").description("list files of a checkup report (metadata only, no content)").option("--type <type>", "filter by file type: json, md").option("--check-id <id>", "filter by check ID (e.g., H002)").addOption(new Option("--debug", "enable debug output").hideHelp()).option("--json", "output raw JSON").action(async (reportId, opts) => {
33726
+ const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Fetching report files...");
33727
+ try {
33728
+ const rootOpts = program2.opts();
33729
+ const cfg = readConfig();
33730
+ const { apiKey } = getConfig(rootOpts);
33731
+ if (!apiKey) {
33732
+ spinner.stop();
33733
+ console.error("API key is required. Run 'pgai auth' first or set --api-key.");
33734
+ process.exitCode = 1;
33735
+ return;
33736
+ }
33737
+ let numericId;
33738
+ if (reportId !== undefined) {
33739
+ numericId = parseInt(reportId, 10);
33740
+ if (isNaN(numericId)) {
33741
+ spinner.stop();
33742
+ console.error("reportId must be a number");
33743
+ process.exitCode = 1;
33744
+ return;
33745
+ }
33746
+ }
33747
+ if (numericId === undefined && !opts.checkId) {
33748
+ spinner.stop();
33749
+ console.error("Either reportId or --check-id is required");
33750
+ process.exitCode = 1;
33751
+ return;
33752
+ }
33753
+ const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
33754
+ const result = await fetchReportFiles2({
33755
+ apiKey,
33756
+ apiBaseUrl,
33757
+ reportId: numericId,
33758
+ type: opts.type,
33759
+ checkId: opts.checkId,
33760
+ debug: !!opts.debug
33761
+ });
33762
+ spinner.stop();
33763
+ printResult(result, opts.json);
33764
+ } catch (err) {
33765
+ spinner.stop();
33766
+ const message = err instanceof Error ? err.message : String(err);
33767
+ console.error(message);
33768
+ process.exitCode = 1;
33769
+ }
33770
+ });
33771
+ reports.command("data [reportId]").description("get checkup report file data (includes content)").option("--type <type>", "filter by file type: json, md").option("--check-id <id>", "filter by check ID (e.g., H002)").option("--formatted", "render markdown with ANSI styling (experimental)").option("-o, --output <dir>", "save files to directory (uses original filenames)").addOption(new Option("--debug", "enable debug output").hideHelp()).option("--json", "output raw JSON").action(async (reportId, opts) => {
33772
+ const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Fetching report data...");
33773
+ try {
33774
+ const rootOpts = program2.opts();
33775
+ const cfg = readConfig();
33776
+ const { apiKey } = getConfig(rootOpts);
33777
+ if (!apiKey) {
33778
+ spinner.stop();
33779
+ console.error("API key is required. Run 'pgai auth' first or set --api-key.");
33780
+ process.exitCode = 1;
33781
+ return;
33782
+ }
33783
+ let numericId;
33784
+ if (reportId !== undefined) {
33785
+ numericId = parseInt(reportId, 10);
33786
+ if (isNaN(numericId)) {
33787
+ spinner.stop();
33788
+ console.error("reportId must be a number");
33789
+ process.exitCode = 1;
33790
+ return;
33791
+ }
33792
+ }
33793
+ if (numericId === undefined && !opts.checkId) {
33794
+ spinner.stop();
33795
+ console.error("Either reportId or --check-id is required");
33796
+ process.exitCode = 1;
33797
+ return;
33798
+ }
33799
+ const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
33800
+ const effectiveType = opts.type ?? (!opts.json && !opts.output ? "md" : undefined);
33801
+ const result = await fetchReportFileData2({
33802
+ apiKey,
33803
+ apiBaseUrl,
33804
+ reportId: numericId,
33805
+ type: effectiveType,
33806
+ checkId: opts.checkId,
33807
+ debug: !!opts.debug
33808
+ });
33809
+ spinner.stop();
33810
+ if (opts.output) {
33811
+ const dir = path6.resolve(opts.output);
33812
+ fs6.mkdirSync(dir, { recursive: true });
33813
+ for (const f of result) {
33814
+ const safeName = path6.basename(f.filename);
33815
+ const filePath = path6.join(dir, safeName);
33816
+ const content = f.type === "json" ? JSON.stringify(tryParseJson(f.data), null, 2) : f.data;
33817
+ fs6.writeFileSync(filePath, content, "utf-8");
33818
+ console.log(filePath);
33819
+ }
33820
+ } else if (opts.json) {
33821
+ const processed = result.map((f) => ({
33822
+ ...f,
33823
+ data: f.type === "json" ? tryParseJson(f.data) : f.data
33824
+ }));
33825
+ printResult(processed, true);
33826
+ } else if (opts.formatted && process.stdout.isTTY) {
33827
+ for (const f of result) {
33828
+ if (result.length > 1) {
33829
+ console.log(`\x1B[1m--- ${f.filename} (${f.check_id}, ${f.type}) ---\x1B[0m`);
33830
+ }
33831
+ if (f.type === "md") {
33832
+ console.log(renderMarkdownForTerminal(f.data));
33833
+ } else if (f.type === "json") {
33834
+ const parsed = tryParseJson(f.data);
33835
+ console.log(typeof parsed === "string" ? parsed : JSON.stringify(parsed, null, 2));
33836
+ } else {
33837
+ console.log(f.data);
33838
+ }
33839
+ }
33840
+ } else {
33841
+ for (const f of result) {
33842
+ if (result.length > 1) {
33843
+ console.log(`--- ${f.filename} (${f.check_id}, ${f.type}) ---`);
33844
+ }
33845
+ console.log(f.data);
33846
+ }
33847
+ }
33848
+ } catch (err) {
33849
+ spinner.stop();
33850
+ const message = err instanceof Error ? err.message : String(err);
33851
+ console.error(message);
33852
+ process.exitCode = 1;
33853
+ }
33854
+ });
33855
+ function tryParseJson(s) {
33856
+ try {
33857
+ return JSON.parse(s);
33858
+ } catch {
33859
+ return s;
33860
+ }
33861
+ }
32084
33862
  var mcp = program2.command("mcp").description("MCP server integration");
32085
33863
  mcp.command("start").description("start MCP stdio server").option("--debug", "enable debug output").action(async (opts) => {
32086
33864
  const rootOpts = program2.opts();
@@ -32119,7 +33897,7 @@ mcp.command("install [client]").description("install MCP server configuration fo
32119
33897
  try {
32120
33898
  let pgaiPath;
32121
33899
  try {
32122
- const execPath = await execPromise("which pgai");
33900
+ const execPath = await execFilePromise("which", ["pgai"]);
32123
33901
  pgaiPath = execPath.stdout.trim();
32124
33902
  } catch {
32125
33903
  pgaiPath = "pgai";
@@ -32127,7 +33905,7 @@ mcp.command("install [client]").description("install MCP server configuration fo
32127
33905
  if (client === "claude-code") {
32128
33906
  console.log("Installing PostgresAI MCP server for Claude Code...");
32129
33907
  try {
32130
- const { stdout, stderr } = await execPromise(`claude mcp add -s user postgresai ${pgaiPath} mcp start`);
33908
+ const { stdout, stderr } = await execFilePromise("claude", ["mcp", "add", "-s", "user", "postgresai", pgaiPath, "mcp", "start"]);
32131
33909
  if (stdout)
32132
33910
  console.log(stdout);
32133
33911
  if (stderr)
@@ -32154,29 +33932,29 @@ mcp.command("install [client]").description("install MCP server configuration fo
32154
33932
  let configDir;
32155
33933
  switch (client) {
32156
33934
  case "cursor":
32157
- configPath = path5.join(homeDir, ".cursor", "mcp.json");
32158
- configDir = path5.dirname(configPath);
33935
+ configPath = path6.join(homeDir, ".cursor", "mcp.json");
33936
+ configDir = path6.dirname(configPath);
32159
33937
  break;
32160
33938
  case "windsurf":
32161
- configPath = path5.join(homeDir, ".windsurf", "mcp.json");
32162
- configDir = path5.dirname(configPath);
33939
+ configPath = path6.join(homeDir, ".windsurf", "mcp.json");
33940
+ configDir = path6.dirname(configPath);
32163
33941
  break;
32164
33942
  case "codex":
32165
- configPath = path5.join(homeDir, ".codex", "mcp.json");
32166
- configDir = path5.dirname(configPath);
33943
+ configPath = path6.join(homeDir, ".codex", "mcp.json");
33944
+ configDir = path6.dirname(configPath);
32167
33945
  break;
32168
33946
  default:
32169
33947
  console.error(`Configuration not implemented for: ${client}`);
32170
33948
  process.exitCode = 1;
32171
33949
  return;
32172
33950
  }
32173
- if (!fs5.existsSync(configDir)) {
32174
- fs5.mkdirSync(configDir, { recursive: true });
33951
+ if (!fs6.existsSync(configDir)) {
33952
+ fs6.mkdirSync(configDir, { recursive: true });
32175
33953
  }
32176
33954
  let config2 = { mcpServers: {} };
32177
- if (fs5.existsSync(configPath)) {
33955
+ if (fs6.existsSync(configPath)) {
32178
33956
  try {
32179
- const content = fs5.readFileSync(configPath, "utf8");
33957
+ const content = fs6.readFileSync(configPath, "utf8");
32180
33958
  config2 = JSON.parse(content);
32181
33959
  if (!config2.mcpServers) {
32182
33960
  config2.mcpServers = {};
@@ -32189,7 +33967,7 @@ mcp.command("install [client]").description("install MCP server configuration fo
32189
33967
  command: pgaiPath,
32190
33968
  args: ["mcp", "start"]
32191
33969
  };
32192
- fs5.writeFileSync(configPath, JSON.stringify(config2, null, 2), "utf8");
33970
+ fs6.writeFileSync(configPath, JSON.stringify(config2, null, 2), "utf8");
32193
33971
  console.log(`\u2713 PostgresAI MCP server configured for ${client}`);
32194
33972
  console.log(` Config file: ${configPath}`);
32195
33973
  console.log("");