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.
@@ -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