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.
- package/README.md +3 -1
- package/bin/postgres-ai.ts +606 -105
- package/bun.lock +4 -4
- package/dist/bin/postgres-ai.js +2355 -577
- package/instances.demo.yml +14 -0
- package/lib/checkup-api.ts +25 -6
- package/lib/checkup.ts +241 -10
- package/lib/config.ts +3 -0
- package/lib/init.ts +196 -4
- package/lib/issues.ts +72 -72
- package/lib/mcp-server.ts +90 -0
- package/lib/metrics-loader.ts +3 -1
- package/lib/reports.ts +373 -0
- package/lib/storage.ts +291 -0
- package/lib/supabase.ts +8 -1
- package/lib/util.ts +7 -1
- package/package.json +4 -4
- package/scripts/embed-checkup-dictionary.ts +9 -0
- package/scripts/embed-metrics.ts +2 -0
- package/test/PERMISSION_CHECK_TEST_SUMMARY.md +139 -0
- package/test/checkup.test.ts +1316 -2
- package/test/compose-cmd.test.ts +120 -0
- package/test/config-consistency.test.ts +321 -5
- package/test/init.integration.test.ts +27 -28
- package/test/init.test.ts +534 -6
- package/test/issues.cli.test.ts +230 -1
- package/test/mcp-server.test.ts +459 -0
- package/test/monitoring.test.ts +78 -0
- package/test/permission-check-sql.test.ts +116 -0
- package/test/reports.cli.test.ts +793 -0
- package/test/reports.test.ts +977 -0
- package/test/schema-validation.test.ts +81 -0
- package/test/storage.test.ts +761 -0
- package/test/test-utils.ts +51 -2
- package/test/upgrade.test.ts +422 -0
package/dist/bin/postgres-ai.js
CHANGED
|
@@ -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: (
|
|
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: (
|
|
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.
|
|
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
|
-
|
|
109
|
+
_collectValue(value, previous) {
|
|
91
110
|
if (previous === this.defaultValue || !Array.isArray(previous)) {
|
|
92
111
|
return [value];
|
|
93
112
|
}
|
|
94
|
-
|
|
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.
|
|
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)
|
|
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)
|
|
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)
|
|
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)
|
|
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
|
-
|
|
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
|
|
314
|
+
const extraDescription = `(${extraInfo.join(", ")})`;
|
|
287
315
|
if (argument.description) {
|
|
288
|
-
return `${argument.description} ${
|
|
316
|
+
return `${argument.description} ${extraDescription}`;
|
|
289
317
|
}
|
|
290
|
-
return
|
|
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
|
|
297
|
-
|
|
298
|
-
|
|
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
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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.
|
|
356
|
+
helper.boxWrap(helper.styleCommandDescription(commandDescription), helpWidth),
|
|
315
357
|
""
|
|
316
358
|
]);
|
|
317
359
|
}
|
|
318
360
|
const argumentList = helper.visibleArguments(cmd).map((argument) => {
|
|
319
|
-
return
|
|
361
|
+
return callFormatItem(helper.styleArgumentTerm(helper.argumentTerm(argument)), helper.styleArgumentDescription(helper.argumentDescription(argument)));
|
|
320
362
|
});
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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 (
|
|
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
|
|
373
|
+
return callFormatItem(helper.styleOptionTerm(helper.optionTerm(option)), helper.styleOptionDescription(helper.optionDescription(option)));
|
|
333
374
|
});
|
|
334
|
-
|
|
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
|
|
343
|
-
|
|
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
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
const
|
|
360
|
-
if (
|
|
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
|
|
363
|
-
const
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
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
|
-
|
|
579
|
+
_collectValue(value, previous) {
|
|
451
580
|
if (previous === this.defaultValue || !Array.isArray(previous)) {
|
|
452
581
|
return [value];
|
|
453
582
|
}
|
|
454
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
522
|
-
|
|
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
|
-
|
|
525
|
-
if (!shortFlag && /^-[^-]$/.test(longFlag)) {
|
|
667
|
+
if (!shortFlag && longFlagExp.test(flagParts[0])) {
|
|
526
668
|
shortFlag = longFlag;
|
|
527
|
-
longFlag =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
933
|
+
argument(name, description, parseArg, defaultValue) {
|
|
763
934
|
const argument = this.createArgument(name, description);
|
|
764
|
-
if (typeof
|
|
765
|
-
argument.default(defaultValue).argParser(
|
|
935
|
+
if (typeof parseArg === "function") {
|
|
936
|
+
argument.default(defaultValue).argParser(parseArg);
|
|
766
937
|
} else {
|
|
767
|
-
argument.default(
|
|
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
|
|
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
|
-
|
|
795
|
-
const [, helpName, helpArgs] =
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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(
|
|
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
|
-
|
|
1416
|
-
|
|
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
|
|
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
|
|
1439
|
-
value = args
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
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
|
-
|
|
1967
|
+
_getOutputContext(contextOptions) {
|
|
1722
1968
|
contextOptions = contextOptions || {};
|
|
1723
|
-
const
|
|
1724
|
-
let
|
|
1725
|
-
|
|
1726
|
-
|
|
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
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
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
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
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
|
-
|
|
2010
|
+
outputContext.write(helpInformation);
|
|
1751
2011
|
if (this._getHelpOption()?.long) {
|
|
1752
2012
|
this.emit(this._getHelpOption().long);
|
|
1753
2013
|
}
|
|
1754
|
-
this.emit("afterHelp",
|
|
1755
|
-
this._getCommandAndAncestors().forEach((command) => command.emit("afterAllHelp",
|
|
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
|
-
|
|
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
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
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
|
|
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.
|
|
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: "^
|
|
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: "^
|
|
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
|
|
15877
|
-
import * as
|
|
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.
|
|
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: "^
|
|
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: "^
|
|
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.
|
|
16119
|
-
console.
|
|
16120
|
-
console.
|
|
16121
|
-
console.
|
|
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.
|
|
16129
|
-
console.
|
|
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.
|
|
16161
|
-
console.
|
|
16162
|
-
console.
|
|
16163
|
-
console.
|
|
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.
|
|
16171
|
-
console.
|
|
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.
|
|
16206
|
-
console.
|
|
16207
|
-
console.
|
|
16208
|
-
console.
|
|
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.
|
|
16216
|
-
console.
|
|
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.
|
|
16279
|
-
console.
|
|
16280
|
-
console.
|
|
16281
|
-
console.
|
|
16282
|
-
console.
|
|
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.
|
|
16291
|
-
console.
|
|
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.
|
|
16334
|
-
console.
|
|
16335
|
-
console.
|
|
16336
|
-
console.
|
|
16337
|
-
console.
|
|
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.
|
|
16346
|
-
console.
|
|
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.
|
|
16397
|
-
console.
|
|
16398
|
-
console.
|
|
16399
|
-
console.
|
|
16400
|
-
console.
|
|
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.
|
|
16409
|
-
console.
|
|
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.
|
|
16449
|
-
console.
|
|
16450
|
-
console.
|
|
16451
|
-
console.
|
|
16452
|
-
console.
|
|
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.
|
|
16461
|
-
console.
|
|
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.
|
|
16501
|
-
console.
|
|
16502
|
-
console.
|
|
16503
|
-
console.
|
|
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.
|
|
16511
|
-
console.
|
|
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.
|
|
16552
|
-
console.
|
|
16553
|
-
console.
|
|
16554
|
-
console.
|
|
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.
|
|
16562
|
-
console.
|
|
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.
|
|
16615
|
-
console.
|
|
16616
|
-
console.
|
|
16617
|
-
console.
|
|
16618
|
-
console.
|
|
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.
|
|
16627
|
-
console.
|
|
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.
|
|
16692
|
-
console.
|
|
16693
|
-
console.
|
|
16694
|
-
console.
|
|
16695
|
-
console.
|
|
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.
|
|
16704
|
-
console.
|
|
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
|
-
|
|
23774
|
-
|
|
23775
|
-
|
|
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.
|
|
23998
|
-
console.
|
|
23999
|
-
console.
|
|
24000
|
-
console.
|
|
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.
|
|
24008
|
-
console.
|
|
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.
|
|
24040
|
-
console.
|
|
24041
|
-
console.
|
|
24042
|
-
console.
|
|
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.
|
|
24050
|
-
console.
|
|
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.
|
|
24085
|
-
console.
|
|
24086
|
-
console.
|
|
24087
|
-
console.
|
|
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.
|
|
24095
|
-
console.
|
|
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.
|
|
24158
|
-
console.
|
|
24159
|
-
console.
|
|
24160
|
-
console.
|
|
24161
|
-
console.
|
|
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.
|
|
24170
|
-
console.
|
|
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.
|
|
24213
|
-
console.
|
|
24214
|
-
console.
|
|
24215
|
-
console.
|
|
24216
|
-
console.
|
|
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.
|
|
24225
|
-
console.
|
|
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.
|
|
24276
|
-
console.
|
|
24277
|
-
console.
|
|
24278
|
-
console.
|
|
24279
|
-
console.
|
|
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.
|
|
24288
|
-
console.
|
|
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.
|
|
24328
|
-
console.
|
|
24329
|
-
console.
|
|
24330
|
-
console.
|
|
24331
|
-
console.
|
|
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.
|
|
24340
|
-
console.
|
|
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.
|
|
24380
|
-
console.
|
|
24381
|
-
console.
|
|
24382
|
-
console.
|
|
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.
|
|
24390
|
-
console.
|
|
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.
|
|
24431
|
-
console.
|
|
24432
|
-
console.
|
|
24433
|
-
console.
|
|
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.
|
|
24441
|
-
console.
|
|
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.
|
|
24494
|
-
console.
|
|
24495
|
-
console.
|
|
24496
|
-
console.
|
|
24497
|
-
console.
|
|
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.
|
|
24506
|
-
console.
|
|
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.
|
|
24571
|
-
console.
|
|
24572
|
-
console.
|
|
24573
|
-
console.
|
|
24574
|
-
console.
|
|
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.
|
|
24583
|
-
console.
|
|
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/
|
|
24592
|
-
function
|
|
24593
|
-
|
|
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
|
-
|
|
24596
|
-
|
|
24597
|
-
|
|
24598
|
-
|
|
24599
|
-
|
|
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
|
-
|
|
24602
|
-
|
|
24603
|
-
|
|
24604
|
-
|
|
24605
|
-
|
|
24606
|
-
|
|
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
|
|
24611
|
-
const
|
|
24612
|
-
|
|
24613
|
-
|
|
24614
|
-
|
|
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
|
-
|
|
24617
|
-
|
|
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 ``;
|
|
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
|
|
24625
|
-
import * as
|
|
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.
|
|
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 =
|
|
25750
|
+
const currentDir = path4.dirname(currentFile);
|
|
24717
25751
|
const candidates = [
|
|
24718
|
-
|
|
24719
|
-
|
|
25752
|
+
path4.resolve(currentDir, "..", "sql"),
|
|
25753
|
+
path4.resolve(currentDir, "..", "..", "sql")
|
|
24720
25754
|
];
|
|
24721
25755
|
for (const candidate of candidates) {
|
|
24722
|
-
if (
|
|
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 =
|
|
24730
|
-
return
|
|
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(
|
|
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(
|
|
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((
|
|
25710
|
-
resolveCallback =
|
|
26867
|
+
const promise2 = new Promise((resolve5, reject) => {
|
|
26868
|
+
resolveCallback = resolve5;
|
|
25711
26869
|
rejectCallback = reject;
|
|
25712
26870
|
});
|
|
25713
|
-
const ready = new Promise((
|
|
25714
|
-
resolveReady =
|
|
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
|
|
25905
|
-
import * as
|
|
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.
|
|
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.
|
|
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.
|
|
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 =
|
|
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 =
|
|
27764
|
-
const fromPkgFile = readTextFileSafe(
|
|
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 =
|
|
27773
|
-
const st =
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
28272
|
-
|
|
28273
|
-
|
|
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((
|
|
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((
|
|
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
|
-
|
|
29818
|
+
resolve6(value);
|
|
28458
29819
|
};
|
|
28459
|
-
|
|
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
|
-
|
|
28830
|
-
|
|
28831
|
-
|
|
28832
|
-
|
|
28833
|
-
|
|
28834
|
-
|
|
28835
|
-
|
|
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((
|
|
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
|
-
|
|
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((
|
|
30255
|
+
return new Promise((resolve7) => {
|
|
28892
30256
|
getReadline().question(prompt, (answer) => {
|
|
28893
|
-
|
|
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
|
|
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 =
|
|
28954
|
-
if (!
|
|
30317
|
+
const outputPath = path6.isAbsolute(outputDir) ? outputDir : path6.resolve(process.cwd(), outputDir);
|
|
30318
|
+
if (!fs6.existsSync(outputPath)) {
|
|
28955
30319
|
try {
|
|
28956
|
-
|
|
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 =
|
|
29036
|
-
|
|
29037
|
-
|
|
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
|
|
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 =
|
|
29098
|
-
const instancesFile =
|
|
29099
|
-
if (!
|
|
29100
|
-
|
|
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 (!
|
|
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
|
-
|
|
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 (!
|
|
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 (
|
|
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
|
-
|
|
30498
|
+
fs6.writeFileSync(instancesFile, header, { encoding: "utf8", mode: 384 });
|
|
29131
30499
|
}
|
|
29132
|
-
const pgwatchConfig =
|
|
29133
|
-
if (!
|
|
29134
|
-
|
|
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 =
|
|
29137
|
-
if (!
|
|
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
|
-
|
|
30509
|
+
fs6.writeFileSync(envFile, envText, { encoding: "utf8", mode: 384 });
|
|
29142
30510
|
}
|
|
29143
|
-
return { fs:
|
|
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.
|
|
30750
|
+
console.error("\u26A0 Optional items missing:");
|
|
29366
30751
|
for (const m of v.missingOptional)
|
|
29367
|
-
console.
|
|
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.
|
|
30873
|
+
console.error("\u26A0 Some optional steps were skipped (not supported or insufficient privileges):");
|
|
29489
30874
|
for (const s of skippedOptional)
|
|
29490
|
-
console.
|
|
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.
|
|
31033
|
+
console.error("\u26A0 Optional items missing:");
|
|
29649
31034
|
for (const m of v.missingOptional)
|
|
29650
|
-
console.
|
|
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.
|
|
31151
|
+
console.error("\u26A0 Some optional steps were skipped (not supported or insufficient privileges):");
|
|
29767
31152
|
for (const s of skippedOptional)
|
|
29768
|
-
console.
|
|
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((
|
|
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) =>
|
|
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.
|
|
31409
|
+
console.error("\u26A0 unprepare-db completed with errors");
|
|
30025
31410
|
console.log(`Applied ${applied.length} steps`);
|
|
30026
|
-
console.
|
|
31411
|
+
console.error("Errors:");
|
|
30027
31412
|
for (const err of errors3) {
|
|
30028
|
-
console.
|
|
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 =
|
|
30328
|
-
if (
|
|
30329
|
-
const instancesFile =
|
|
30330
|
-
return { fs:
|
|
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 =
|
|
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.
|
|
31779
|
+
console.error(`
|
|
30381
31780
|
Debug: Registering monitoring instance...`);
|
|
30382
|
-
console.
|
|
30383
|
-
console.
|
|
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.
|
|
30399
|
-
console.
|
|
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.
|
|
31803
|
+
console.error(`Debug: Monitoring registration response: ${body}`);
|
|
30405
31804
|
}
|
|
30406
31805
|
}).catch((err) => {
|
|
30407
31806
|
if (debug) {
|
|
30408
|
-
console.
|
|
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 (
|
|
30415
|
-
const stats =
|
|
31813
|
+
if (fs6.existsSync(configPath)) {
|
|
31814
|
+
const stats = fs6.statSync(configPath);
|
|
30416
31815
|
if (!stats.isDirectory()) {
|
|
30417
|
-
const content =
|
|
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
|
-
|
|
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 =
|
|
30460
|
-
if (
|
|
31858
|
+
const cfgPath = path6.resolve(projectDir, ".pgwatch-config");
|
|
31859
|
+
if (fs6.existsSync(cfgPath)) {
|
|
30461
31860
|
try {
|
|
30462
|
-
const stats =
|
|
31861
|
+
const stats = fs6.statSync(cfgPath);
|
|
30463
31862
|
if (!stats.isDirectory()) {
|
|
30464
|
-
const content =
|
|
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((
|
|
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) =>
|
|
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 =
|
|
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 =
|
|
31932
|
+
const envFile = path6.resolve(projectDir, ".env");
|
|
30514
31933
|
let existingRegistry = null;
|
|
30515
31934
|
let existingPassword = null;
|
|
30516
|
-
|
|
30517
|
-
|
|
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
|
-
|
|
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.
|
|
30542
|
-
console.
|
|
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.
|
|
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(
|
|
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.
|
|
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(
|
|
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.
|
|
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.
|
|
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.
|
|
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:
|
|
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
|
-
|
|
30621
|
-
console.log(`Instances file: ${
|
|
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
|
-
|
|
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
|
-
|
|
30657
|
-
|
|
30658
|
-
|
|
30659
|
-
|
|
30660
|
-
|
|
30661
|
-
|
|
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
|
-
|
|
30664
|
-
|
|
30665
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
30712
|
-
|
|
30713
|
-
|
|
30714
|
-
|
|
30715
|
-
|
|
30716
|
-
|
|
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
|
-
|
|
30719
|
-
|
|
30720
|
-
|
|
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.
|
|
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.
|
|
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(
|
|
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 =
|
|
32209
|
+
const cfgPath = path6.resolve(projectDir, ".pgwatch-config");
|
|
30748
32210
|
let grafanaPassword = "";
|
|
32211
|
+
let vmAuthUsername = "";
|
|
32212
|
+
let vmAuthPassword = "";
|
|
30749
32213
|
try {
|
|
30750
|
-
if (
|
|
30751
|
-
const stats =
|
|
32214
|
+
if (fs6.existsSync(cfgPath)) {
|
|
32215
|
+
const stats = fs6.statSync(cfgPath);
|
|
30752
32216
|
if (!stats.isDirectory()) {
|
|
30753
|
-
const content =
|
|
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
|
|
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 (
|
|
30767
|
-
const stats =
|
|
32229
|
+
if (fs6.existsSync(cfgPath)) {
|
|
32230
|
+
const stats = fs6.statSync(cfgPath);
|
|
30768
32231
|
if (!stats.isDirectory()) {
|
|
30769
|
-
configContent =
|
|
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
|
-
|
|
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.
|
|
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((
|
|
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((
|
|
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 (
|
|
32483
|
+
if (fs6.existsSync(instancesFile) && !fs6.lstatSync(instancesFile).isDirectory()) {
|
|
30980
32484
|
console.log(`
|
|
30981
32485
|
Instances configuration:
|
|
30982
32486
|
`);
|
|
30983
|
-
const text =
|
|
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 =
|
|
30999
|
-
if (!
|
|
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
|
|
31006
|
-
const { stdout: branch } = await
|
|
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
|
|
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.
|
|
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 (!
|
|
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 =
|
|
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 (
|
|
31210
|
-
const content2 =
|
|
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
|
|
31223
|
-
|
|
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 =
|
|
31242
|
-
|
|
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 (!
|
|
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 =
|
|
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
|
-
|
|
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 (!
|
|
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 =
|
|
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.
|
|
31345
|
-
console.
|
|
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.
|
|
31365
|
-
console.
|
|
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.
|
|
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 =
|
|
33045
|
+
const hasNewConfig = fs6.existsSync(newConfigPath);
|
|
31537
33046
|
let legacyPath;
|
|
31538
33047
|
try {
|
|
31539
33048
|
const { projectDir } = await resolveOrInitPaths();
|
|
31540
|
-
legacyPath =
|
|
33049
|
+
legacyPath = path6.resolve(projectDir, ".pgwatch-config");
|
|
31541
33050
|
} catch {
|
|
31542
|
-
legacyPath =
|
|
33051
|
+
legacyPath = path6.resolve(process.cwd(), ".pgwatch-config");
|
|
31543
33052
|
}
|
|
31544
|
-
const hasLegacyConfig =
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
33078
|
+
const cfgPath = path6.resolve(projectDir, ".pgwatch-config");
|
|
31570
33079
|
try {
|
|
31571
|
-
const { stdout: password } = await
|
|
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 (
|
|
31581
|
-
const stats =
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
31613
|
-
if (!
|
|
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 =
|
|
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 =
|
|
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.
|
|
33249
|
+
console.error(`Debug: Original content: ${JSON.stringify(content)}`);
|
|
31730
33250
|
}
|
|
31731
33251
|
content = interpretEscapes2(content);
|
|
31732
33252
|
if (opts.debug) {
|
|
31733
|
-
console.
|
|
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.
|
|
33399
|
+
console.error(`Debug: Original content: ${JSON.stringify(content)}`);
|
|
31880
33400
|
}
|
|
31881
33401
|
content = interpretEscapes2(content);
|
|
31882
33402
|
if (opts.debug) {
|
|
31883
|
-
console.
|
|
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
|
|
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
|
|
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 =
|
|
32158
|
-
configDir =
|
|
33935
|
+
configPath = path6.join(homeDir, ".cursor", "mcp.json");
|
|
33936
|
+
configDir = path6.dirname(configPath);
|
|
32159
33937
|
break;
|
|
32160
33938
|
case "windsurf":
|
|
32161
|
-
configPath =
|
|
32162
|
-
configDir =
|
|
33939
|
+
configPath = path6.join(homeDir, ".windsurf", "mcp.json");
|
|
33940
|
+
configDir = path6.dirname(configPath);
|
|
32163
33941
|
break;
|
|
32164
33942
|
case "codex":
|
|
32165
|
-
configPath =
|
|
32166
|
-
configDir =
|
|
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 (!
|
|
32174
|
-
|
|
33951
|
+
if (!fs6.existsSync(configDir)) {
|
|
33952
|
+
fs6.mkdirSync(configDir, { recursive: true });
|
|
32175
33953
|
}
|
|
32176
33954
|
let config2 = { mcpServers: {} };
|
|
32177
|
-
if (
|
|
33955
|
+
if (fs6.existsSync(configPath)) {
|
|
32178
33956
|
try {
|
|
32179
|
-
const content =
|
|
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
|
-
|
|
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("");
|