postgresai 0.15.0-dev.2 → 0.15.0-dev.4

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.
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env bun
2
2
 
3
- import { Command } from "commander";
3
+ import { Command, Option } from "commander";
4
4
  import pkg from "../package.json";
5
5
  import * as config from "../lib/config";
6
6
  import * as yaml from "js-yaml";
@@ -13,6 +13,7 @@ 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
14
  import { fetchReports, fetchAllReports, fetchReportFiles, fetchReportFileData, renderMarkdownForTerminal, parseFlexibleDate } from "../lib/reports";
15
15
  import { resolveBaseUrls } from "../lib/util";
16
+ import { uploadFile, downloadFile, buildMarkdownLink } from "../lib/storage";
16
17
  import { applyInitPlan, applyUninitPlan, buildInitPlan, buildUninitPlan, connectWithSslFallback, DEFAULT_MONITORING_USER, KNOWN_PROVIDERS, redactPasswordsInSql, resolveAdminConnection, resolveMonitoringPassword, validateProvider, verifyInitSetup } from "../lib/init";
17
18
  import { SupabaseClient, resolveSupabaseConfig, extractProjectRefFromUrl, applyInitPlanViaSupabase, verifyInitSetupViaSupabase, fetchPoolerDatabaseUrl, type PgCompatibleError } from "../lib/supabase";
18
19
  import * as pkce from "../lib/pkce";
@@ -343,7 +344,8 @@ function writeReportFiles(reports: Record<string, any>, outputPath: string): voi
343
344
  for (const [checkId, report] of Object.entries(reports)) {
344
345
  const filePath = path.join(outputPath, `${checkId}.json`);
345
346
  fs.writeFileSync(filePath, JSON.stringify(report, null, 2), "utf8");
346
- console.log(`✓ ${checkId}: ${filePath}`);
347
+ const title = report.checkTitle || checkId;
348
+ console.log(`✓ ${checkId} ${title}: ${filePath}`);
347
349
  }
348
350
  }
349
351
 
@@ -411,6 +413,7 @@ interface CliOptions {
411
413
  apiKey?: string;
412
414
  apiBaseUrl?: string;
413
415
  uiBaseUrl?: string;
416
+ storageBaseUrl?: string;
414
417
  }
415
418
 
416
419
  /**
@@ -579,6 +582,10 @@ program
579
582
  .option(
580
583
  "--ui-base-url <url>",
581
584
  "UI base URL for browser routes (overrides PGAI_UI_BASE_URL)"
585
+ )
586
+ .option(
587
+ "--storage-base-url <url>",
588
+ "Storage base URL for file uploads (overrides PGAI_STORAGE_BASE_URL)"
582
589
  );
583
590
 
584
591
  program
@@ -595,6 +602,27 @@ program
595
602
  console.log(`Default project saved: ${value}`);
596
603
  });
597
604
 
605
+ program
606
+ .command("set-storage-url <url>")
607
+ .description("store storage base URL for file uploads")
608
+ .action(async (url: string) => {
609
+ const value = (url || "").trim();
610
+ if (!value) {
611
+ console.error("Error: url is required");
612
+ process.exitCode = 1;
613
+ return;
614
+ }
615
+ try {
616
+ const { normalizeBaseUrl } = await import("../lib/util");
617
+ const normalized = normalizeBaseUrl(value);
618
+ config.writeConfig({ storageBaseUrl: normalized });
619
+ console.log(`Storage URL saved: ${normalized}`);
620
+ } catch {
621
+ console.error(`Error: invalid URL: ${value}`);
622
+ process.exitCode = 1;
623
+ }
624
+ });
625
+
598
626
  program
599
627
  .command("prepare-db [conn]")
600
628
  .description("prepare database for monitoring: create monitoring user, required view(s), and grant permissions (idempotent)")
@@ -840,8 +868,8 @@ program
840
868
  } else {
841
869
  console.log("✓ prepare-db verify: OK");
842
870
  if (v.missingOptional.length > 0) {
843
- console.log("⚠ Optional items missing:");
844
- for (const m of v.missingOptional) console.log(`- ${m}`);
871
+ console.error("⚠ Optional items missing:");
872
+ for (const m of v.missingOptional) console.error(`- ${m}`);
845
873
  }
846
874
  }
847
875
  return;
@@ -972,8 +1000,8 @@ program
972
1000
  } else {
973
1001
  console.log(opts.resetPassword ? "✓ prepare-db password reset completed" : "✓ prepare-db completed");
974
1002
  if (skippedOptional.length > 0) {
975
- console.log("⚠ Some optional steps were skipped (not supported or insufficient privileges):");
976
- for (const s of skippedOptional) console.log(`- ${s}`);
1003
+ console.error("⚠ Some optional steps were skipped (not supported or insufficient privileges):");
1004
+ for (const s of skippedOptional) console.error(`- ${s}`);
977
1005
  }
978
1006
  if (process.stdout.isTTY) {
979
1007
  console.log(`Applied ${applied.length} steps`);
@@ -1154,8 +1182,8 @@ program
1154
1182
  } else {
1155
1183
  console.log(`✓ prepare-db verify: OK${opts.provider ? ` (provider: ${opts.provider})` : ""}`);
1156
1184
  if (v.missingOptional.length > 0) {
1157
- console.log("⚠ Optional items missing:");
1158
- for (const m of v.missingOptional) console.log(`- ${m}`);
1185
+ console.error("⚠ Optional items missing:");
1186
+ for (const m of v.missingOptional) console.error(`- ${m}`);
1159
1187
  }
1160
1188
  }
1161
1189
  return;
@@ -1282,8 +1310,8 @@ program
1282
1310
  } else {
1283
1311
  console.log(opts.resetPassword ? "✓ prepare-db password reset completed" : "✓ prepare-db completed");
1284
1312
  if (skippedOptional.length > 0) {
1285
- console.log("⚠ Some optional steps were skipped (not supported or insufficient privileges):");
1286
- for (const s of skippedOptional) console.log(`- ${s}`);
1313
+ console.error("⚠ Some optional steps were skipped (not supported or insufficient privileges):");
1314
+ for (const s of skippedOptional) console.error(`- ${s}`);
1287
1315
  }
1288
1316
  // Keep output compact but still useful
1289
1317
  if (process.stdout.isTTY) {
@@ -1599,11 +1627,11 @@ program
1599
1627
  console.log("✓ unprepare-db completed");
1600
1628
  console.log(`Applied ${applied.length} steps`);
1601
1629
  } else {
1602
- console.log("⚠ unprepare-db completed with errors");
1630
+ console.error("⚠ unprepare-db completed with errors");
1603
1631
  console.log(`Applied ${applied.length} steps`);
1604
- console.log("Errors:");
1632
+ console.error("Errors:");
1605
1633
  for (const err of errors) {
1606
- console.log(` - ${err}`);
1634
+ console.error(` - ${err}`);
1607
1635
  }
1608
1636
  process.exitCode = 1;
1609
1637
  }
@@ -1925,8 +1953,8 @@ program
1925
1953
  }
1926
1954
  }
1927
1955
 
1928
- // Output JSON to stdout
1929
- if (shouldPrintJson) {
1956
+ // Output JSON to stdout (unless --output is specified, in which case files are written instead)
1957
+ if (shouldPrintJson && !outputPath) {
1930
1958
  console.log(JSON.stringify(reports, null, 2));
1931
1959
  }
1932
1960
 
@@ -2088,9 +2116,9 @@ function registerMonitoringInstance(
2088
2116
  const debug = opts?.debug;
2089
2117
 
2090
2118
  if (debug) {
2091
- console.log(`\nDebug: Registering monitoring instance...`);
2092
- console.log(`Debug: POST ${url}`);
2093
- console.log(`Debug: project_name=${projectName}`);
2119
+ console.error(`\nDebug: Registering monitoring instance...`);
2120
+ console.error(`Debug: POST ${url}`);
2121
+ console.error(`Debug: project_name=${projectName}`);
2094
2122
  }
2095
2123
 
2096
2124
  // Fire and forget - don't block the main flow
@@ -2108,18 +2136,18 @@ function registerMonitoringInstance(
2108
2136
  const body = await res.text().catch(() => "");
2109
2137
  if (!res.ok) {
2110
2138
  if (debug) {
2111
- console.log(`Debug: Monitoring registration failed: HTTP ${res.status}`);
2112
- console.log(`Debug: Response: ${body}`);
2139
+ console.error(`Debug: Monitoring registration failed: HTTP ${res.status}`);
2140
+ console.error(`Debug: Response: ${body}`);
2113
2141
  }
2114
2142
  return;
2115
2143
  }
2116
2144
  if (debug) {
2117
- console.log(`Debug: Monitoring registration response: ${body}`);
2145
+ console.error(`Debug: Monitoring registration response: ${body}`);
2118
2146
  }
2119
2147
  })
2120
2148
  .catch((err) => {
2121
2149
  if (debug) {
2122
- console.log(`Debug: Monitoring registration error: ${err.message}`);
2150
+ console.error(`Debug: Monitoring registration error: ${err.message}`);
2123
2151
  }
2124
2152
  });
2125
2153
  }
@@ -2299,8 +2327,8 @@ mon
2299
2327
 
2300
2328
  // Validate conflicting options
2301
2329
  if (opts.demo && opts.dbUrl) {
2302
- console.log("⚠ Both --demo and --db-url provided. Demo mode includes its own database.");
2303
- console.log("⚠ The --db-url will be ignored in demo mode.\n");
2330
+ console.error("⚠ Both --demo and --db-url provided. Demo mode includes its own database.");
2331
+ console.error("⚠ The --db-url will be ignored in demo mode.\n");
2304
2332
  opts.dbUrl = undefined;
2305
2333
  }
2306
2334
 
@@ -2316,7 +2344,7 @@ mon
2316
2344
  // Check if containers are already running
2317
2345
  const { running, containers } = checkRunningContainers();
2318
2346
  if (running) {
2319
- console.log(`⚠ Monitoring services are already running: ${containers.join(", ")}`);
2347
+ console.error(`⚠ Monitoring services are already running: ${containers.join(", ")}`);
2320
2348
  console.log("Use 'postgres-ai mon restart' to restart them\n");
2321
2349
  return;
2322
2350
  }
@@ -2335,7 +2363,7 @@ mon
2335
2363
  } else if (opts.yes) {
2336
2364
  // Auto-yes mode without API key - skip API key setup
2337
2365
  console.log("Auto-yes mode: no API key provided, skipping API key setup");
2338
- console.log("⚠ Reports will be generated locally only");
2366
+ console.error("⚠ Reports will be generated locally only");
2339
2367
  console.log("You can add an API key later with: postgres-ai add-key <api_key>\n");
2340
2368
  } else {
2341
2369
  const answer = await question("Do you have a Postgres AI API key? (Y/n): ");
@@ -2355,16 +2383,16 @@ mon
2355
2383
  break;
2356
2384
  }
2357
2385
 
2358
- console.log("⚠ API key cannot be empty");
2386
+ console.error("⚠ API key cannot be empty");
2359
2387
  const retry = await question("Try again or skip API key setup, retry? (Y/n): ");
2360
2388
  if (retry.toLowerCase() === "n") {
2361
- console.log("⚠ Skipping API key setup - reports will be generated locally only");
2389
+ console.error("⚠ Skipping API key setup - reports will be generated locally only");
2362
2390
  console.log("You can add an API key later with: postgres-ai add-key <api_key>\n");
2363
2391
  break;
2364
2392
  }
2365
2393
  }
2366
2394
  } else {
2367
- console.log("⚠ Skipping API key setup - reports will be generated locally only");
2395
+ console.error("⚠ Skipping API key setup - reports will be generated locally only");
2368
2396
  console.log("You can add an API key later with: postgres-ai add-key <api_key>\n");
2369
2397
  }
2370
2398
  }
@@ -2424,7 +2452,7 @@ mon
2424
2452
  } else if (opts.yes) {
2425
2453
  // Auto-yes mode without database URL - skip database setup
2426
2454
  console.log("Auto-yes mode: no database URL provided, skipping database setup");
2427
- console.log("⚠ No PostgreSQL instance added");
2455
+ console.error("⚠ No PostgreSQL instance added");
2428
2456
  console.log("You can add one later with: postgres-ai mon targets add\n");
2429
2457
  } else {
2430
2458
  console.log("You need to add at least one PostgreSQL instance to monitor");
@@ -2442,7 +2470,7 @@ mon
2442
2470
  const m = connStr.match(/^postgresql:\/\/([^:]+):([^@]+)@([^:\/]+)(?::(\d+))?\/(.+)$/);
2443
2471
  if (!m) {
2444
2472
  console.error("✗ Invalid connection string format");
2445
- console.log("⚠ Continuing without adding instance\n");
2473
+ console.error("⚠ Continuing without adding instance\n");
2446
2474
  } else {
2447
2475
  const host = m[3];
2448
2476
  const db = m[5];
@@ -2467,10 +2495,10 @@ mon
2467
2495
  }
2468
2496
  }
2469
2497
  } else {
2470
- console.log("⚠ No PostgreSQL instance added - you can add one later with: postgres-ai mon targets add\n");
2498
+ console.error("⚠ No PostgreSQL instance added - you can add one later with: postgres-ai mon targets add\n");
2471
2499
  }
2472
2500
  } else {
2473
- console.log("⚠ No PostgreSQL instance added - you can add one later with: postgres-ai mon targets add\n");
2501
+ console.error("⚠ No PostgreSQL instance added - you can add one later with: postgres-ai mon targets add\n");
2474
2502
  }
2475
2503
  }
2476
2504
  } else {
@@ -2523,7 +2551,7 @@ mon
2523
2551
 
2524
2552
  console.log("✓ Grafana password configured\n");
2525
2553
  } catch (error) {
2526
- console.log("⚠ Could not generate Grafana password automatically");
2554
+ console.error("⚠ Could not generate Grafana password automatically");
2527
2555
  console.log("Using default password: demo\n");
2528
2556
  grafanaPassword = "demo";
2529
2557
  }
@@ -2915,7 +2943,7 @@ mon
2915
2943
  if (downCode === 0) {
2916
2944
  console.log("✓ Monitoring services stopped and removed");
2917
2945
  } else {
2918
- console.log("⚠ Could not stop services (may not be running)");
2946
+ console.error("⚠ Could not stop services (may not be running)");
2919
2947
  }
2920
2948
 
2921
2949
  // Remove any orphaned containers that docker compose down missed
@@ -3228,8 +3256,8 @@ auth
3228
3256
  const { apiBaseUrl, uiBaseUrl } = resolveBaseUrls(rootOpts, cfg);
3229
3257
 
3230
3258
  if (opts.debug) {
3231
- console.log(`Debug: Resolved API base URL: ${apiBaseUrl}`);
3232
- console.log(`Debug: Resolved UI base URL: ${uiBaseUrl}`);
3259
+ console.error(`Debug: Resolved API base URL: ${apiBaseUrl}`);
3260
+ console.error(`Debug: Resolved UI base URL: ${uiBaseUrl}`);
3233
3261
  }
3234
3262
 
3235
3263
  try {
@@ -3259,8 +3287,8 @@ auth
3259
3287
  const initUrl = new URL(`${apiBaseUrl}/rpc/oauth_init`);
3260
3288
 
3261
3289
  if (opts.debug) {
3262
- console.log(`Debug: Trying to POST to: ${initUrl.toString()}`);
3263
- console.log(`Debug: Request data: ${initData}`);
3290
+ console.error(`Debug: Trying to POST to: ${initUrl.toString()}`);
3291
+ console.error(`Debug: Request data: ${initData}`);
3264
3292
  }
3265
3293
 
3266
3294
  // Step 2: Initialize OAuth session on backend using fetch
@@ -3306,7 +3334,7 @@ auth
3306
3334
  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)}`;
3307
3335
 
3308
3336
  if (opts.debug) {
3309
- console.log(`Debug: Auth URL: ${authUrl}`);
3337
+ console.error(`Debug: Auth URL: ${authUrl}`);
3310
3338
  }
3311
3339
 
3312
3340
  console.log(`\nOpening browser for authentication...`);
@@ -3730,12 +3758,12 @@ issues
3730
3758
  // Interpret escape sequences in content (e.g., \n -> newline)
3731
3759
  if (opts.debug) {
3732
3760
  // eslint-disable-next-line no-console
3733
- console.log(`Debug: Original content: ${JSON.stringify(content)}`);
3761
+ console.error(`Debug: Original content: ${JSON.stringify(content)}`);
3734
3762
  }
3735
3763
  content = interpretEscapes(content);
3736
3764
  if (opts.debug) {
3737
3765
  // eslint-disable-next-line no-console
3738
- console.log(`Debug: Interpreted content: ${JSON.stringify(content)}`);
3766
+ console.error(`Debug: Interpreted content: ${JSON.stringify(content)}`);
3739
3767
  }
3740
3768
 
3741
3769
  const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Posting comment...");
@@ -3929,12 +3957,12 @@ issues
3929
3957
  .action(async (commentId: string, content: string, opts: { debug?: boolean; json?: boolean }) => {
3930
3958
  if (opts.debug) {
3931
3959
  // eslint-disable-next-line no-console
3932
- console.log(`Debug: Original content: ${JSON.stringify(content)}`);
3960
+ console.error(`Debug: Original content: ${JSON.stringify(content)}`);
3933
3961
  }
3934
3962
  content = interpretEscapes(content);
3935
3963
  if (opts.debug) {
3936
3964
  // eslint-disable-next-line no-console
3937
- console.log(`Debug: Interpreted content: ${JSON.stringify(content)}`);
3965
+ console.error(`Debug: Interpreted content: ${JSON.stringify(content)}`);
3938
3966
  }
3939
3967
 
3940
3968
  const rootOpts = program.opts<CliOptions>();
@@ -3967,6 +3995,93 @@ issues
3967
3995
  }
3968
3996
  });
3969
3997
 
3998
+ // File upload/download (subcommands of issues)
3999
+ const issueFiles = issues.command("files").description("upload and download files for issues");
4000
+
4001
+ issueFiles
4002
+ .command("upload <path>")
4003
+ .description("upload a file to storage and get a markdown link")
4004
+ .option("--debug", "enable debug output")
4005
+ .option("--json", "output raw JSON")
4006
+ .action(async (filePath: string, opts: { debug?: boolean; json?: boolean }) => {
4007
+ const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Uploading file...");
4008
+ try {
4009
+ const rootOpts = program.opts<CliOptions>();
4010
+ const cfg = config.readConfig();
4011
+ const { apiKey } = getConfig(rootOpts);
4012
+ if (!apiKey) {
4013
+ spinner.stop();
4014
+ console.error("API key is required. Run 'pgai auth' first or set --api-key.");
4015
+ process.exitCode = 1;
4016
+ return;
4017
+ }
4018
+
4019
+ const { storageBaseUrl } = resolveBaseUrls(rootOpts, cfg);
4020
+
4021
+ const result = await uploadFile({
4022
+ apiKey,
4023
+ storageBaseUrl,
4024
+ filePath,
4025
+ debug: !!opts.debug,
4026
+ });
4027
+ spinner.stop();
4028
+
4029
+ if (opts.json) {
4030
+ printResult(result, true);
4031
+ } else {
4032
+ const md = buildMarkdownLink(result.url, storageBaseUrl, result.metadata.originalName);
4033
+ const displayUrl = result.url.startsWith("/") ? `${storageBaseUrl}${result.url}` : `${storageBaseUrl}/${result.url}`;
4034
+ console.log(`URL: ${displayUrl}`);
4035
+ console.log(`File: ${result.metadata.originalName}`);
4036
+ console.log(`Size: ${result.metadata.size} bytes`);
4037
+ console.log(`Type: ${result.metadata.mimeType}`);
4038
+ console.log(`Markdown: ${md}`);
4039
+ }
4040
+ } catch (err) {
4041
+ spinner.stop();
4042
+ const message = err instanceof Error ? err.message : String(err);
4043
+ console.error(message);
4044
+ process.exitCode = 1;
4045
+ }
4046
+ });
4047
+
4048
+ issueFiles
4049
+ .command("download <url>")
4050
+ .description("download a file from storage")
4051
+ .option("-o, --output <path>", "output file path (default: derive from URL)")
4052
+ .option("--debug", "enable debug output")
4053
+ .action(async (fileUrl: string, opts: { output?: string; debug?: boolean }) => {
4054
+ const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Downloading file...");
4055
+ try {
4056
+ const rootOpts = program.opts<CliOptions>();
4057
+ const cfg = config.readConfig();
4058
+ const { apiKey } = getConfig(rootOpts);
4059
+ if (!apiKey) {
4060
+ spinner.stop();
4061
+ console.error("API key is required. Run 'pgai auth' first or set --api-key.");
4062
+ process.exitCode = 1;
4063
+ return;
4064
+ }
4065
+
4066
+ const { storageBaseUrl } = resolveBaseUrls(rootOpts, cfg);
4067
+
4068
+ const result = await downloadFile({
4069
+ apiKey,
4070
+ storageBaseUrl,
4071
+ fileUrl,
4072
+ outputPath: opts.output,
4073
+ debug: !!opts.debug,
4074
+ });
4075
+ spinner.stop();
4076
+ console.log(`Saved: ${result.savedTo}`);
4077
+ } catch (err) {
4078
+ spinner.stop();
4079
+ const message = err instanceof Error ? err.message : String(err);
4080
+ console.error(message);
4081
+ process.exitCode = 1;
4082
+ }
4083
+ });
4084
+
3970
4085
  // Action Items management (subcommands of issues)
3971
4086
  issues
3972
4087
  .command("action-items <issueId>")
@@ -4198,11 +4313,11 @@ reports
4198
4313
  .command("list")
4199
4314
  .description("list checkup reports")
4200
4315
  .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))
4316
+ .addOption(new Option("--status <status>", "filter by status (e.g., completed)").hideHelp())
4317
+ .option("--limit <n>", "max number of reports to return (default: 20, max: 100)", (v: string) => { const n = parseInt(v, 10); return Number.isNaN(n) ? 20 : Math.max(1, Math.min(n, 100)); })
4203
4318
  .option("--before <date>", "show reports created before this date (YYYY-MM-DD, DD.MM.YYYY, etc.)")
4204
4319
  .option("--all", "fetch all reports (paginated automatically)")
4205
- .option("--debug", "enable debug output")
4320
+ .addOption(new Option("--debug", "enable debug output").hideHelp())
4206
4321
  .option("--json", "output raw JSON")
4207
4322
  .action(async (opts: { projectId?: number; status?: string; limit?: number; before?: string; all?: boolean; debug?: boolean; json?: boolean }) => {
4208
4323
  const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Fetching reports...");
@@ -4260,7 +4375,7 @@ reports
4260
4375
  .description("list files of a checkup report (metadata only, no content)")
4261
4376
  .option("--type <type>", "filter by file type: json, md")
4262
4377
  .option("--check-id <id>", "filter by check ID (e.g., H002)")
4263
- .option("--debug", "enable debug output")
4378
+ .addOption(new Option("--debug", "enable debug output").hideHelp())
4264
4379
  .option("--json", "output raw JSON")
4265
4380
  .action(async (reportId: string | undefined, opts: { type?: "json" | "md"; checkId?: string; debug?: boolean; json?: boolean }) => {
4266
4381
  const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Fetching report files...");
@@ -4317,7 +4432,7 @@ reports
4317
4432
  .option("--check-id <id>", "filter by check ID (e.g., H002)")
4318
4433
  .option("--formatted", "render markdown with ANSI styling (experimental)")
4319
4434
  .option("-o, --output <dir>", "save files to directory (uses original filenames)")
4320
- .option("--debug", "enable debug output")
4435
+ .addOption(new Option("--debug", "enable debug output").hideHelp())
4321
4436
  .option("--json", "output raw JSON")
4322
4437
  .action(async (reportId: string | undefined, opts: { type?: "json" | "md"; checkId?: string; formatted?: boolean; output?: string; debug?: boolean; json?: boolean }) => {
4323
4438
  const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Fetching report data...");