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.
- package/bin/postgres-ai.ts +164 -49
- package/dist/bin/postgres-ai.js +736 -389
- package/lib/checkup.ts +16 -10
- package/lib/config.ts +3 -0
- package/lib/init.ts +1 -1
- package/lib/issues.ts +72 -72
- package/lib/reports.ts +12 -12
- package/lib/storage.ts +291 -0
- package/lib/util.ts +7 -1
- package/package.json +1 -1
- package/test/checkup.test.ts +28 -0
- package/test/init.test.ts +1 -1
- package/test/issues.cli.test.ts +230 -1
- package/test/mcp-server.test.ts +69 -0
- package/test/reports.cli.test.ts +84 -0
- package/test/reports.test.ts +3 -3
- package/test/storage.test.ts +761 -0
package/bin/postgres-ai.ts
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
844
|
-
for (const m of v.missingOptional) console.
|
|
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.
|
|
976
|
-
for (const s of skippedOptional) console.
|
|
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.
|
|
1158
|
-
for (const m of v.missingOptional) console.
|
|
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.
|
|
1286
|
-
for (const s of skippedOptional) console.
|
|
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.
|
|
1630
|
+
console.error("⚠ unprepare-db completed with errors");
|
|
1603
1631
|
console.log(`Applied ${applied.length} steps`);
|
|
1604
|
-
console.
|
|
1632
|
+
console.error("Errors:");
|
|
1605
1633
|
for (const err of errors) {
|
|
1606
|
-
console.
|
|
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.
|
|
2092
|
-
console.
|
|
2093
|
-
console.
|
|
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.
|
|
2112
|
-
console.
|
|
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.
|
|
2145
|
+
console.error(`Debug: Monitoring registration response: ${body}`);
|
|
2118
2146
|
}
|
|
2119
2147
|
})
|
|
2120
2148
|
.catch((err) => {
|
|
2121
2149
|
if (debug) {
|
|
2122
|
-
console.
|
|
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.
|
|
2303
|
-
console.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
3232
|
-
console.
|
|
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.
|
|
3263
|
-
console.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
.
|
|
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
|
-
.
|
|
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
|
-
.
|
|
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
|
-
.
|
|
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...");
|