postgresai 0.15.0-dev.1 → 0.15.0-dev.2
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/bin/postgres-ai.ts +223 -0
- package/dist/bin/postgres-ai.js +698 -2
- package/lib/mcp-server.ts +90 -0
- package/lib/reports.ts +373 -0
- package/package.json +1 -1
- package/test/mcp-server.test.ts +390 -0
- package/test/reports.cli.test.ts +709 -0
- package/test/reports.test.ts +977 -0
package/bin/postgres-ai.ts
CHANGED
|
@@ -11,6 +11,7 @@ import * as crypto from "node:crypto";
|
|
|
11
11
|
import { Client } from "pg";
|
|
12
12
|
import { startMcpServer } from "../lib/mcp-server";
|
|
13
13
|
import { fetchIssues, fetchIssueComments, createIssueComment, fetchIssue, createIssue, updateIssue, updateIssueComment, fetchActionItem, fetchActionItems, createActionItem, updateActionItem, type ConfigChange } from "../lib/issues";
|
|
14
|
+
import { fetchReports, fetchAllReports, fetchReportFiles, fetchReportFileData, renderMarkdownForTerminal, parseFlexibleDate } from "../lib/reports";
|
|
14
15
|
import { resolveBaseUrls } from "../lib/util";
|
|
15
16
|
import { applyInitPlan, applyUninitPlan, buildInitPlan, buildUninitPlan, connectWithSslFallback, DEFAULT_MONITORING_USER, KNOWN_PROVIDERS, redactPasswordsInSql, resolveAdminConnection, resolveMonitoringPassword, validateProvider, verifyInitSetup } from "../lib/init";
|
|
16
17
|
import { SupabaseClient, resolveSupabaseConfig, extractProjectRefFromUrl, applyInitPlanViaSupabase, verifyInitSetupViaSupabase, fetchPoolerDatabaseUrl, type PgCompatibleError } from "../lib/supabase";
|
|
@@ -4190,6 +4191,228 @@ issues
|
|
|
4190
4191
|
}
|
|
4191
4192
|
});
|
|
4192
4193
|
|
|
4194
|
+
// Reports management
|
|
4195
|
+
const reports = program.command("reports").description("checkup reports management");
|
|
4196
|
+
|
|
4197
|
+
reports
|
|
4198
|
+
.command("list")
|
|
4199
|
+
.description("list checkup reports")
|
|
4200
|
+
.option("--project-id <id>", "filter by project id", (v: string) => parseInt(v, 10))
|
|
4201
|
+
.option("--status <status>", "filter by status (e.g., completed)")
|
|
4202
|
+
.option("--limit <n>", "max number of reports to return (default: 20)", (v: string) => parseInt(v, 10))
|
|
4203
|
+
.option("--before <date>", "show reports created before this date (YYYY-MM-DD, DD.MM.YYYY, etc.)")
|
|
4204
|
+
.option("--all", "fetch all reports (paginated automatically)")
|
|
4205
|
+
.option("--debug", "enable debug output")
|
|
4206
|
+
.option("--json", "output raw JSON")
|
|
4207
|
+
.action(async (opts: { projectId?: number; status?: string; limit?: number; before?: string; all?: boolean; debug?: boolean; json?: boolean }) => {
|
|
4208
|
+
const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Fetching reports...");
|
|
4209
|
+
try {
|
|
4210
|
+
const rootOpts = program.opts<CliOptions>();
|
|
4211
|
+
const cfg = config.readConfig();
|
|
4212
|
+
const { apiKey } = getConfig(rootOpts);
|
|
4213
|
+
if (!apiKey) {
|
|
4214
|
+
spinner.stop();
|
|
4215
|
+
console.error("API key is required. Run 'pgai auth' first or set --api-key.");
|
|
4216
|
+
process.exitCode = 1;
|
|
4217
|
+
return;
|
|
4218
|
+
}
|
|
4219
|
+
if (opts.all && opts.before) {
|
|
4220
|
+
spinner.stop();
|
|
4221
|
+
console.error("--all and --before cannot be used together");
|
|
4222
|
+
process.exitCode = 1;
|
|
4223
|
+
return;
|
|
4224
|
+
}
|
|
4225
|
+
const { apiBaseUrl } = resolveBaseUrls(rootOpts, cfg);
|
|
4226
|
+
|
|
4227
|
+
let result;
|
|
4228
|
+
if (opts.all) {
|
|
4229
|
+
result = await fetchAllReports({
|
|
4230
|
+
apiKey,
|
|
4231
|
+
apiBaseUrl,
|
|
4232
|
+
projectId: opts.projectId,
|
|
4233
|
+
status: opts.status,
|
|
4234
|
+
limit: opts.limit,
|
|
4235
|
+
debug: !!opts.debug,
|
|
4236
|
+
});
|
|
4237
|
+
} else {
|
|
4238
|
+
result = await fetchReports({
|
|
4239
|
+
apiKey,
|
|
4240
|
+
apiBaseUrl,
|
|
4241
|
+
projectId: opts.projectId,
|
|
4242
|
+
status: opts.status,
|
|
4243
|
+
limit: opts.limit,
|
|
4244
|
+
beforeDate: opts.before ? parseFlexibleDate(opts.before) : undefined,
|
|
4245
|
+
debug: !!opts.debug,
|
|
4246
|
+
});
|
|
4247
|
+
}
|
|
4248
|
+
spinner.stop();
|
|
4249
|
+
printResult(result, opts.json);
|
|
4250
|
+
} catch (err) {
|
|
4251
|
+
spinner.stop();
|
|
4252
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4253
|
+
console.error(message);
|
|
4254
|
+
process.exitCode = 1;
|
|
4255
|
+
}
|
|
4256
|
+
});
|
|
4257
|
+
|
|
4258
|
+
reports
|
|
4259
|
+
.command("files [reportId]")
|
|
4260
|
+
.description("list files of a checkup report (metadata only, no content)")
|
|
4261
|
+
.option("--type <type>", "filter by file type: json, md")
|
|
4262
|
+
.option("--check-id <id>", "filter by check ID (e.g., H002)")
|
|
4263
|
+
.option("--debug", "enable debug output")
|
|
4264
|
+
.option("--json", "output raw JSON")
|
|
4265
|
+
.action(async (reportId: string | undefined, opts: { type?: "json" | "md"; checkId?: string; debug?: boolean; json?: boolean }) => {
|
|
4266
|
+
const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Fetching report files...");
|
|
4267
|
+
try {
|
|
4268
|
+
const rootOpts = program.opts<CliOptions>();
|
|
4269
|
+
const cfg = config.readConfig();
|
|
4270
|
+
const { apiKey } = getConfig(rootOpts);
|
|
4271
|
+
if (!apiKey) {
|
|
4272
|
+
spinner.stop();
|
|
4273
|
+
console.error("API key is required. Run 'pgai auth' first or set --api-key.");
|
|
4274
|
+
process.exitCode = 1;
|
|
4275
|
+
return;
|
|
4276
|
+
}
|
|
4277
|
+
let numericId: number | undefined;
|
|
4278
|
+
if (reportId !== undefined) {
|
|
4279
|
+
numericId = parseInt(reportId, 10);
|
|
4280
|
+
if (isNaN(numericId)) {
|
|
4281
|
+
spinner.stop();
|
|
4282
|
+
console.error("reportId must be a number");
|
|
4283
|
+
process.exitCode = 1;
|
|
4284
|
+
return;
|
|
4285
|
+
}
|
|
4286
|
+
}
|
|
4287
|
+
if (numericId === undefined && !opts.checkId) {
|
|
4288
|
+
spinner.stop();
|
|
4289
|
+
console.error("Either reportId or --check-id is required");
|
|
4290
|
+
process.exitCode = 1;
|
|
4291
|
+
return;
|
|
4292
|
+
}
|
|
4293
|
+
const { apiBaseUrl } = resolveBaseUrls(rootOpts, cfg);
|
|
4294
|
+
|
|
4295
|
+
const result = await fetchReportFiles({
|
|
4296
|
+
apiKey,
|
|
4297
|
+
apiBaseUrl,
|
|
4298
|
+
reportId: numericId,
|
|
4299
|
+
type: opts.type,
|
|
4300
|
+
checkId: opts.checkId,
|
|
4301
|
+
debug: !!opts.debug,
|
|
4302
|
+
});
|
|
4303
|
+
spinner.stop();
|
|
4304
|
+
printResult(result, opts.json);
|
|
4305
|
+
} catch (err) {
|
|
4306
|
+
spinner.stop();
|
|
4307
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4308
|
+
console.error(message);
|
|
4309
|
+
process.exitCode = 1;
|
|
4310
|
+
}
|
|
4311
|
+
});
|
|
4312
|
+
|
|
4313
|
+
reports
|
|
4314
|
+
.command("data [reportId]")
|
|
4315
|
+
.description("get checkup report file data (includes content)")
|
|
4316
|
+
.option("--type <type>", "filter by file type: json, md")
|
|
4317
|
+
.option("--check-id <id>", "filter by check ID (e.g., H002)")
|
|
4318
|
+
.option("--formatted", "render markdown with ANSI styling (experimental)")
|
|
4319
|
+
.option("-o, --output <dir>", "save files to directory (uses original filenames)")
|
|
4320
|
+
.option("--debug", "enable debug output")
|
|
4321
|
+
.option("--json", "output raw JSON")
|
|
4322
|
+
.action(async (reportId: string | undefined, opts: { type?: "json" | "md"; checkId?: string; formatted?: boolean; output?: string; debug?: boolean; json?: boolean }) => {
|
|
4323
|
+
const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Fetching report data...");
|
|
4324
|
+
try {
|
|
4325
|
+
const rootOpts = program.opts<CliOptions>();
|
|
4326
|
+
const cfg = config.readConfig();
|
|
4327
|
+
const { apiKey } = getConfig(rootOpts);
|
|
4328
|
+
if (!apiKey) {
|
|
4329
|
+
spinner.stop();
|
|
4330
|
+
console.error("API key is required. Run 'pgai auth' first or set --api-key.");
|
|
4331
|
+
process.exitCode = 1;
|
|
4332
|
+
return;
|
|
4333
|
+
}
|
|
4334
|
+
let numericId: number | undefined;
|
|
4335
|
+
if (reportId !== undefined) {
|
|
4336
|
+
numericId = parseInt(reportId, 10);
|
|
4337
|
+
if (isNaN(numericId)) {
|
|
4338
|
+
spinner.stop();
|
|
4339
|
+
console.error("reportId must be a number");
|
|
4340
|
+
process.exitCode = 1;
|
|
4341
|
+
return;
|
|
4342
|
+
}
|
|
4343
|
+
}
|
|
4344
|
+
if (numericId === undefined && !opts.checkId) {
|
|
4345
|
+
spinner.stop();
|
|
4346
|
+
console.error("Either reportId or --check-id is required");
|
|
4347
|
+
process.exitCode = 1;
|
|
4348
|
+
return;
|
|
4349
|
+
}
|
|
4350
|
+
const { apiBaseUrl } = resolveBaseUrls(rootOpts, cfg);
|
|
4351
|
+
|
|
4352
|
+
// Default to "md" for terminal output (human-readable); --json and --output get all types
|
|
4353
|
+
const effectiveType = opts.type ?? (!opts.json && !opts.output ? "md" as const : undefined);
|
|
4354
|
+
const result = await fetchReportFileData({
|
|
4355
|
+
apiKey,
|
|
4356
|
+
apiBaseUrl,
|
|
4357
|
+
reportId: numericId,
|
|
4358
|
+
type: effectiveType,
|
|
4359
|
+
checkId: opts.checkId,
|
|
4360
|
+
debug: !!opts.debug,
|
|
4361
|
+
});
|
|
4362
|
+
spinner.stop();
|
|
4363
|
+
|
|
4364
|
+
if (opts.output) {
|
|
4365
|
+
const dir = path.resolve(opts.output);
|
|
4366
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
4367
|
+
for (const f of result) {
|
|
4368
|
+
const safeName = path.basename(f.filename);
|
|
4369
|
+
const filePath = path.join(dir, safeName);
|
|
4370
|
+
const content = f.type === "json"
|
|
4371
|
+
? JSON.stringify(tryParseJson(f.data), null, 2)
|
|
4372
|
+
: f.data;
|
|
4373
|
+
fs.writeFileSync(filePath, content, "utf-8");
|
|
4374
|
+
console.log(filePath);
|
|
4375
|
+
}
|
|
4376
|
+
} else if (opts.json) {
|
|
4377
|
+
const processed = result.map((f) => ({
|
|
4378
|
+
...f,
|
|
4379
|
+
data: f.type === "json" ? tryParseJson(f.data) : f.data,
|
|
4380
|
+
}));
|
|
4381
|
+
printResult(processed, true);
|
|
4382
|
+
} else if (opts.formatted && process.stdout.isTTY) {
|
|
4383
|
+
for (const f of result) {
|
|
4384
|
+
if (result.length > 1) {
|
|
4385
|
+
console.log(`\x1b[1m--- ${f.filename} (${f.check_id}, ${f.type}) ---\x1b[0m`);
|
|
4386
|
+
}
|
|
4387
|
+
if (f.type === "md") {
|
|
4388
|
+
console.log(renderMarkdownForTerminal(f.data));
|
|
4389
|
+
} else if (f.type === "json") {
|
|
4390
|
+
const parsed = tryParseJson(f.data);
|
|
4391
|
+
console.log(typeof parsed === "string" ? parsed : JSON.stringify(parsed, null, 2));
|
|
4392
|
+
} else {
|
|
4393
|
+
console.log(f.data);
|
|
4394
|
+
}
|
|
4395
|
+
}
|
|
4396
|
+
} else {
|
|
4397
|
+
for (const f of result) {
|
|
4398
|
+
if (result.length > 1) {
|
|
4399
|
+
console.log(`--- ${f.filename} (${f.check_id}, ${f.type}) ---`);
|
|
4400
|
+
}
|
|
4401
|
+
console.log(f.data);
|
|
4402
|
+
}
|
|
4403
|
+
}
|
|
4404
|
+
} catch (err) {
|
|
4405
|
+
spinner.stop();
|
|
4406
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4407
|
+
console.error(message);
|
|
4408
|
+
process.exitCode = 1;
|
|
4409
|
+
}
|
|
4410
|
+
});
|
|
4411
|
+
|
|
4412
|
+
function tryParseJson(s: string): unknown {
|
|
4413
|
+
try { return JSON.parse(s); } catch { return s; }
|
|
4414
|
+
}
|
|
4415
|
+
|
|
4193
4416
|
// MCP server
|
|
4194
4417
|
const mcp = program.command("mcp").description("MCP server integration");
|
|
4195
4418
|
|