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/dist/bin/postgres-ai.js
CHANGED
|
@@ -13064,7 +13064,7 @@ var {
|
|
|
13064
13064
|
// package.json
|
|
13065
13065
|
var package_default = {
|
|
13066
13066
|
name: "postgresai",
|
|
13067
|
-
version: "0.15.0-dev.
|
|
13067
|
+
version: "0.15.0-dev.2",
|
|
13068
13068
|
description: "postgres_ai CLI",
|
|
13069
13069
|
license: "Apache-2.0",
|
|
13070
13070
|
private: false,
|
|
@@ -15892,7 +15892,7 @@ var Result = import_lib.default.Result;
|
|
|
15892
15892
|
var TypeOverrides = import_lib.default.TypeOverrides;
|
|
15893
15893
|
var defaults = import_lib.default.defaults;
|
|
15894
15894
|
// package.json
|
|
15895
|
-
var version = "0.15.0-dev.
|
|
15895
|
+
var version = "0.15.0-dev.2";
|
|
15896
15896
|
var package_default2 = {
|
|
15897
15897
|
name: "postgresai",
|
|
15898
15898
|
version,
|
|
@@ -16709,6 +16709,192 @@ async function updateActionItem(params) {
|
|
|
16709
16709
|
}
|
|
16710
16710
|
}
|
|
16711
16711
|
|
|
16712
|
+
// lib/reports.ts
|
|
16713
|
+
function parseFlexibleDate(input) {
|
|
16714
|
+
const s = input.trim();
|
|
16715
|
+
const dotMatch = s.match(/^(\d{1,2})\.(\d{1,2})\.(\d{4})(?:\s+(\d{1,2}):(\d{2})(?::(\d{2}))?)?$/);
|
|
16716
|
+
if (dotMatch) {
|
|
16717
|
+
const [, dd, mm, yyyy, hh, min, ss] = dotMatch;
|
|
16718
|
+
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`;
|
|
16719
|
+
const d = new Date(iso);
|
|
16720
|
+
if (isNaN(d.getTime()))
|
|
16721
|
+
throw new Error(`Invalid date: ${input}`);
|
|
16722
|
+
return d.toISOString();
|
|
16723
|
+
}
|
|
16724
|
+
const isoMatch = s.match(/^(\d{4})-(\d{2})-(\d{2})(?:[T ](\d{2}):(\d{2})(?::(\d{2}))?)?$/);
|
|
16725
|
+
if (isoMatch) {
|
|
16726
|
+
const [, yyyy, mm, dd, hh, min, ss] = isoMatch;
|
|
16727
|
+
const iso = `${yyyy}-${mm}-${dd}T${hh ?? "00"}:${min ?? "00"}:${ss ?? "00"}Z`;
|
|
16728
|
+
const d = new Date(iso);
|
|
16729
|
+
if (isNaN(d.getTime()))
|
|
16730
|
+
throw new Error(`Invalid date: ${input}`);
|
|
16731
|
+
return d.toISOString();
|
|
16732
|
+
}
|
|
16733
|
+
throw new Error(`Unrecognized date format: ${input}. Use YYYY-MM-DD or DD.MM.YYYY`);
|
|
16734
|
+
}
|
|
16735
|
+
async function fetchReports(params) {
|
|
16736
|
+
const { apiKey, apiBaseUrl, projectId, status, limit = 20, beforeDate, beforeId, debug } = params;
|
|
16737
|
+
if (!apiKey) {
|
|
16738
|
+
throw new Error("API key is required");
|
|
16739
|
+
}
|
|
16740
|
+
const base = normalizeBaseUrl(apiBaseUrl);
|
|
16741
|
+
const url = new URL(`${base}/checkup_reports`);
|
|
16742
|
+
url.searchParams.set("order", "id.desc");
|
|
16743
|
+
url.searchParams.set("limit", String(limit));
|
|
16744
|
+
if (typeof projectId === "number") {
|
|
16745
|
+
url.searchParams.set("project_id", `eq.${projectId}`);
|
|
16746
|
+
}
|
|
16747
|
+
if (status) {
|
|
16748
|
+
url.searchParams.set("status", `eq.${status}`);
|
|
16749
|
+
}
|
|
16750
|
+
if (beforeDate) {
|
|
16751
|
+
url.searchParams.set("created_at", `lt.${beforeDate}`);
|
|
16752
|
+
}
|
|
16753
|
+
if (typeof beforeId === "number") {
|
|
16754
|
+
url.searchParams.set("id", `lt.${beforeId}`);
|
|
16755
|
+
}
|
|
16756
|
+
const headers = {
|
|
16757
|
+
"access-token": apiKey,
|
|
16758
|
+
Prefer: "return=representation",
|
|
16759
|
+
"Content-Type": "application/json",
|
|
16760
|
+
Connection: "close"
|
|
16761
|
+
};
|
|
16762
|
+
if (debug) {
|
|
16763
|
+
const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
|
|
16764
|
+
console.log(`Debug: Resolved API base URL: ${base}`);
|
|
16765
|
+
console.log(`Debug: GET URL: ${url.toString()}`);
|
|
16766
|
+
console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
|
|
16767
|
+
}
|
|
16768
|
+
const response = await fetch(url.toString(), { method: "GET", headers });
|
|
16769
|
+
if (debug) {
|
|
16770
|
+
console.log(`Debug: Response status: ${response.status}`);
|
|
16771
|
+
}
|
|
16772
|
+
const data = await response.text();
|
|
16773
|
+
if (response.ok) {
|
|
16774
|
+
try {
|
|
16775
|
+
return JSON.parse(data);
|
|
16776
|
+
} catch {
|
|
16777
|
+
throw new Error(`Failed to parse reports response: ${data}`);
|
|
16778
|
+
}
|
|
16779
|
+
} else {
|
|
16780
|
+
throw new Error(formatHttpError("Failed to fetch reports", response.status, data));
|
|
16781
|
+
}
|
|
16782
|
+
}
|
|
16783
|
+
var MAX_ALL_REPORTS = 1e4;
|
|
16784
|
+
async function fetchAllReports(params) {
|
|
16785
|
+
const pageSize = params.limit ?? 100;
|
|
16786
|
+
const all = [];
|
|
16787
|
+
let beforeId;
|
|
16788
|
+
while (true) {
|
|
16789
|
+
const page = await fetchReports({ ...params, limit: pageSize, beforeId });
|
|
16790
|
+
if (page.length === 0)
|
|
16791
|
+
break;
|
|
16792
|
+
all.push(...page);
|
|
16793
|
+
if (all.length >= MAX_ALL_REPORTS) {
|
|
16794
|
+
console.warn(`Warning: reached maximum of ${MAX_ALL_REPORTS} reports, stopping pagination`);
|
|
16795
|
+
break;
|
|
16796
|
+
}
|
|
16797
|
+
beforeId = page[page.length - 1].id;
|
|
16798
|
+
if (page.length < pageSize)
|
|
16799
|
+
break;
|
|
16800
|
+
}
|
|
16801
|
+
return all;
|
|
16802
|
+
}
|
|
16803
|
+
async function fetchReportFiles(params) {
|
|
16804
|
+
const { apiKey, apiBaseUrl, reportId, type: type2, checkId, debug } = params;
|
|
16805
|
+
if (!apiKey) {
|
|
16806
|
+
throw new Error("API key is required");
|
|
16807
|
+
}
|
|
16808
|
+
if (reportId === undefined && !checkId) {
|
|
16809
|
+
throw new Error("Either reportId or checkId is required");
|
|
16810
|
+
}
|
|
16811
|
+
const base = normalizeBaseUrl(apiBaseUrl);
|
|
16812
|
+
const url = new URL(`${base}/checkup_report_files`);
|
|
16813
|
+
if (typeof reportId === "number") {
|
|
16814
|
+
url.searchParams.set("checkup_report_id", `eq.${reportId}`);
|
|
16815
|
+
}
|
|
16816
|
+
url.searchParams.set("order", "id.asc");
|
|
16817
|
+
if (type2) {
|
|
16818
|
+
url.searchParams.set("type", `eq.${type2}`);
|
|
16819
|
+
}
|
|
16820
|
+
if (checkId) {
|
|
16821
|
+
url.searchParams.set("check_id", `eq.${checkId}`);
|
|
16822
|
+
}
|
|
16823
|
+
const headers = {
|
|
16824
|
+
"access-token": apiKey,
|
|
16825
|
+
Prefer: "return=representation",
|
|
16826
|
+
"Content-Type": "application/json",
|
|
16827
|
+
Connection: "close"
|
|
16828
|
+
};
|
|
16829
|
+
if (debug) {
|
|
16830
|
+
const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
|
|
16831
|
+
console.log(`Debug: Resolved API base URL: ${base}`);
|
|
16832
|
+
console.log(`Debug: GET URL: ${url.toString()}`);
|
|
16833
|
+
console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
|
|
16834
|
+
}
|
|
16835
|
+
const response = await fetch(url.toString(), { method: "GET", headers });
|
|
16836
|
+
if (debug) {
|
|
16837
|
+
console.log(`Debug: Response status: ${response.status}`);
|
|
16838
|
+
}
|
|
16839
|
+
const data = await response.text();
|
|
16840
|
+
if (response.ok) {
|
|
16841
|
+
try {
|
|
16842
|
+
return JSON.parse(data);
|
|
16843
|
+
} catch {
|
|
16844
|
+
throw new Error(`Failed to parse report files response: ${data}`);
|
|
16845
|
+
}
|
|
16846
|
+
} else {
|
|
16847
|
+
throw new Error(formatHttpError("Failed to fetch report files", response.status, data));
|
|
16848
|
+
}
|
|
16849
|
+
}
|
|
16850
|
+
async function fetchReportFileData(params) {
|
|
16851
|
+
const { apiKey, apiBaseUrl, reportId, type: type2, checkId, debug } = params;
|
|
16852
|
+
if (!apiKey) {
|
|
16853
|
+
throw new Error("API key is required");
|
|
16854
|
+
}
|
|
16855
|
+
if (reportId === undefined && !checkId) {
|
|
16856
|
+
throw new Error("Either reportId or checkId is required");
|
|
16857
|
+
}
|
|
16858
|
+
const base = normalizeBaseUrl(apiBaseUrl);
|
|
16859
|
+
const url = new URL(`${base}/checkup_report_file_data`);
|
|
16860
|
+
if (typeof reportId === "number") {
|
|
16861
|
+
url.searchParams.set("checkup_report_id", `eq.${reportId}`);
|
|
16862
|
+
}
|
|
16863
|
+
url.searchParams.set("order", "id.asc");
|
|
16864
|
+
if (type2) {
|
|
16865
|
+
url.searchParams.set("type", `eq.${type2}`);
|
|
16866
|
+
}
|
|
16867
|
+
if (checkId) {
|
|
16868
|
+
url.searchParams.set("check_id", `eq.${checkId}`);
|
|
16869
|
+
}
|
|
16870
|
+
const headers = {
|
|
16871
|
+
"access-token": apiKey,
|
|
16872
|
+
Prefer: "return=representation",
|
|
16873
|
+
"Content-Type": "application/json",
|
|
16874
|
+
Connection: "close"
|
|
16875
|
+
};
|
|
16876
|
+
if (debug) {
|
|
16877
|
+
const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
|
|
16878
|
+
console.log(`Debug: Resolved API base URL: ${base}`);
|
|
16879
|
+
console.log(`Debug: GET URL: ${url.toString()}`);
|
|
16880
|
+
console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
|
|
16881
|
+
}
|
|
16882
|
+
const response = await fetch(url.toString(), { method: "GET", headers });
|
|
16883
|
+
if (debug) {
|
|
16884
|
+
console.log(`Debug: Response status: ${response.status}`);
|
|
16885
|
+
}
|
|
16886
|
+
const data = await response.text();
|
|
16887
|
+
if (response.ok) {
|
|
16888
|
+
try {
|
|
16889
|
+
return JSON.parse(data);
|
|
16890
|
+
} catch {
|
|
16891
|
+
throw new Error(`Failed to parse report file data response: ${data}`);
|
|
16892
|
+
}
|
|
16893
|
+
} else {
|
|
16894
|
+
throw new Error(formatHttpError("Failed to fetch report file data", response.status, data));
|
|
16895
|
+
}
|
|
16896
|
+
}
|
|
16897
|
+
|
|
16712
16898
|
// node_modules/zod/v4/core/core.js
|
|
16713
16899
|
var NEVER = Object.freeze({
|
|
16714
16900
|
status: "aborted"
|
|
@@ -23770,6 +23956,46 @@ async function handleToolCall(req, rootOpts, extra) {
|
|
|
23770
23956
|
await updateActionItem({ apiKey, apiBaseUrl, actionItemId, title, description, isDone, status, statusReason, debug });
|
|
23771
23957
|
return { content: [{ type: "text", text: JSON.stringify({ success: true }, null, 2) }] };
|
|
23772
23958
|
}
|
|
23959
|
+
if (toolName === "list_reports") {
|
|
23960
|
+
const projectId = args.project_id !== undefined ? Number(args.project_id) : undefined;
|
|
23961
|
+
const status = args.status ? String(args.status) : undefined;
|
|
23962
|
+
const limit = args.limit !== undefined ? Number(args.limit) : undefined;
|
|
23963
|
+
const beforeDate = args.before_date ? parseFlexibleDate(String(args.before_date)) : undefined;
|
|
23964
|
+
const all = args.all === true;
|
|
23965
|
+
let reports;
|
|
23966
|
+
if (all) {
|
|
23967
|
+
reports = await fetchAllReports({ apiKey, apiBaseUrl, projectId, status, limit, debug });
|
|
23968
|
+
} else {
|
|
23969
|
+
reports = await fetchReports({ apiKey, apiBaseUrl, projectId, status, limit, beforeDate, debug });
|
|
23970
|
+
}
|
|
23971
|
+
return { content: [{ type: "text", text: JSON.stringify(reports, null, 2) }] };
|
|
23972
|
+
}
|
|
23973
|
+
if (toolName === "list_report_files") {
|
|
23974
|
+
const reportId = args.report_id !== undefined ? Number(args.report_id) : undefined;
|
|
23975
|
+
if (reportId !== undefined && isNaN(reportId)) {
|
|
23976
|
+
return { content: [{ type: "text", text: "report_id must be a number" }], isError: true };
|
|
23977
|
+
}
|
|
23978
|
+
const type2 = args.type ? String(args.type) : undefined;
|
|
23979
|
+
const checkId = args.check_id ? String(args.check_id) : undefined;
|
|
23980
|
+
if (reportId === undefined && !checkId) {
|
|
23981
|
+
return { content: [{ type: "text", text: "Either report_id or check_id is required" }], isError: true };
|
|
23982
|
+
}
|
|
23983
|
+
const files = await fetchReportFiles({ apiKey, apiBaseUrl, reportId, type: type2, checkId, debug });
|
|
23984
|
+
return { content: [{ type: "text", text: JSON.stringify(files, null, 2) }] };
|
|
23985
|
+
}
|
|
23986
|
+
if (toolName === "get_report_data") {
|
|
23987
|
+
const reportId = args.report_id !== undefined ? Number(args.report_id) : undefined;
|
|
23988
|
+
if (reportId !== undefined && isNaN(reportId)) {
|
|
23989
|
+
return { content: [{ type: "text", text: "report_id must be a number" }], isError: true };
|
|
23990
|
+
}
|
|
23991
|
+
const type2 = args.type ? String(args.type) : undefined;
|
|
23992
|
+
const checkId = args.check_id ? String(args.check_id) : undefined;
|
|
23993
|
+
if (reportId === undefined && !checkId) {
|
|
23994
|
+
return { content: [{ type: "text", text: "Either report_id or check_id is required" }], isError: true };
|
|
23995
|
+
}
|
|
23996
|
+
const files = await fetchReportFileData({ apiKey, apiBaseUrl, reportId, type: type2, checkId, debug });
|
|
23997
|
+
return { content: [{ type: "text", text: JSON.stringify(files, null, 2) }] };
|
|
23998
|
+
}
|
|
23773
23999
|
throw new Error(`Unknown tool: ${toolName}`);
|
|
23774
24000
|
} catch (err) {
|
|
23775
24001
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -23955,6 +24181,50 @@ async function startMcpServer(rootOpts, extra) {
|
|
|
23955
24181
|
required: ["action_item_id"],
|
|
23956
24182
|
additionalProperties: false
|
|
23957
24183
|
}
|
|
24184
|
+
},
|
|
24185
|
+
{
|
|
24186
|
+
name: "list_reports",
|
|
24187
|
+
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.",
|
|
24188
|
+
inputSchema: {
|
|
24189
|
+
type: "object",
|
|
24190
|
+
properties: {
|
|
24191
|
+
project_id: { type: "number", description: "Filter by project ID" },
|
|
24192
|
+
status: { type: "string", description: "Filter by status (e.g., 'completed')" },
|
|
24193
|
+
limit: { type: "number", description: "Max number of reports to return (default: 20)" },
|
|
24194
|
+
before_date: { type: "string", description: "Show reports created before this date (YYYY-MM-DD, DD.MM.YYYY, YYYY-MM-DD HH:mm, etc.)" },
|
|
24195
|
+
all: { type: "boolean", description: "Fetch all reports (paginated automatically)" },
|
|
24196
|
+
debug: { type: "boolean", description: "Enable verbose debug logs" }
|
|
24197
|
+
},
|
|
24198
|
+
additionalProperties: false
|
|
24199
|
+
}
|
|
24200
|
+
},
|
|
24201
|
+
{
|
|
24202
|
+
name: "list_report_files",
|
|
24203
|
+
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.",
|
|
24204
|
+
inputSchema: {
|
|
24205
|
+
type: "object",
|
|
24206
|
+
properties: {
|
|
24207
|
+
report_id: { type: "number", description: "Checkup report ID (optional if check_id is provided)" },
|
|
24208
|
+
type: { type: "string", description: "Filter by file type: 'json' or 'md'" },
|
|
24209
|
+
check_id: { type: "string", description: "Filter by check ID (e.g., 'H002', 'F004')" },
|
|
24210
|
+
debug: { type: "boolean", description: "Enable verbose debug logs" }
|
|
24211
|
+
},
|
|
24212
|
+
additionalProperties: false
|
|
24213
|
+
}
|
|
24214
|
+
},
|
|
24215
|
+
{
|
|
24216
|
+
name: "get_report_data",
|
|
24217
|
+
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.",
|
|
24218
|
+
inputSchema: {
|
|
24219
|
+
type: "object",
|
|
24220
|
+
properties: {
|
|
24221
|
+
report_id: { type: "number", description: "Checkup report ID (optional if check_id is provided)" },
|
|
24222
|
+
type: { type: "string", description: "Filter by file type: 'json' for raw data, 'md' for markdown analysis" },
|
|
24223
|
+
check_id: { type: "string", description: "Filter by check ID (e.g., 'H002', 'F004')" },
|
|
24224
|
+
debug: { type: "boolean", description: "Enable verbose debug logs" }
|
|
24225
|
+
},
|
|
24226
|
+
additionalProperties: false
|
|
24227
|
+
}
|
|
23958
24228
|
}
|
|
23959
24229
|
]
|
|
23960
24230
|
};
|
|
@@ -24588,6 +24858,245 @@ async function updateActionItem2(params) {
|
|
|
24588
24858
|
}
|
|
24589
24859
|
}
|
|
24590
24860
|
|
|
24861
|
+
// lib/reports.ts
|
|
24862
|
+
function parseFlexibleDate2(input) {
|
|
24863
|
+
const s = input.trim();
|
|
24864
|
+
const dotMatch = s.match(/^(\d{1,2})\.(\d{1,2})\.(\d{4})(?:\s+(\d{1,2}):(\d{2})(?::(\d{2}))?)?$/);
|
|
24865
|
+
if (dotMatch) {
|
|
24866
|
+
const [, dd, mm, yyyy, hh, min, ss] = dotMatch;
|
|
24867
|
+
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`;
|
|
24868
|
+
const d = new Date(iso);
|
|
24869
|
+
if (isNaN(d.getTime()))
|
|
24870
|
+
throw new Error(`Invalid date: ${input}`);
|
|
24871
|
+
return d.toISOString();
|
|
24872
|
+
}
|
|
24873
|
+
const isoMatch = s.match(/^(\d{4})-(\d{2})-(\d{2})(?:[T ](\d{2}):(\d{2})(?::(\d{2}))?)?$/);
|
|
24874
|
+
if (isoMatch) {
|
|
24875
|
+
const [, yyyy, mm, dd, hh, min, ss] = isoMatch;
|
|
24876
|
+
const iso = `${yyyy}-${mm}-${dd}T${hh ?? "00"}:${min ?? "00"}:${ss ?? "00"}Z`;
|
|
24877
|
+
const d = new Date(iso);
|
|
24878
|
+
if (isNaN(d.getTime()))
|
|
24879
|
+
throw new Error(`Invalid date: ${input}`);
|
|
24880
|
+
return d.toISOString();
|
|
24881
|
+
}
|
|
24882
|
+
throw new Error(`Unrecognized date format: ${input}. Use YYYY-MM-DD or DD.MM.YYYY`);
|
|
24883
|
+
}
|
|
24884
|
+
async function fetchReports2(params) {
|
|
24885
|
+
const { apiKey, apiBaseUrl, projectId, status, limit = 20, beforeDate, beforeId, debug } = params;
|
|
24886
|
+
if (!apiKey) {
|
|
24887
|
+
throw new Error("API key is required");
|
|
24888
|
+
}
|
|
24889
|
+
const base = normalizeBaseUrl(apiBaseUrl);
|
|
24890
|
+
const url = new URL(`${base}/checkup_reports`);
|
|
24891
|
+
url.searchParams.set("order", "id.desc");
|
|
24892
|
+
url.searchParams.set("limit", String(limit));
|
|
24893
|
+
if (typeof projectId === "number") {
|
|
24894
|
+
url.searchParams.set("project_id", `eq.${projectId}`);
|
|
24895
|
+
}
|
|
24896
|
+
if (status) {
|
|
24897
|
+
url.searchParams.set("status", `eq.${status}`);
|
|
24898
|
+
}
|
|
24899
|
+
if (beforeDate) {
|
|
24900
|
+
url.searchParams.set("created_at", `lt.${beforeDate}`);
|
|
24901
|
+
}
|
|
24902
|
+
if (typeof beforeId === "number") {
|
|
24903
|
+
url.searchParams.set("id", `lt.${beforeId}`);
|
|
24904
|
+
}
|
|
24905
|
+
const headers = {
|
|
24906
|
+
"access-token": apiKey,
|
|
24907
|
+
Prefer: "return=representation",
|
|
24908
|
+
"Content-Type": "application/json",
|
|
24909
|
+
Connection: "close"
|
|
24910
|
+
};
|
|
24911
|
+
if (debug) {
|
|
24912
|
+
const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
|
|
24913
|
+
console.log(`Debug: Resolved API base URL: ${base}`);
|
|
24914
|
+
console.log(`Debug: GET URL: ${url.toString()}`);
|
|
24915
|
+
console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
|
|
24916
|
+
}
|
|
24917
|
+
const response = await fetch(url.toString(), { method: "GET", headers });
|
|
24918
|
+
if (debug) {
|
|
24919
|
+
console.log(`Debug: Response status: ${response.status}`);
|
|
24920
|
+
}
|
|
24921
|
+
const data = await response.text();
|
|
24922
|
+
if (response.ok) {
|
|
24923
|
+
try {
|
|
24924
|
+
return JSON.parse(data);
|
|
24925
|
+
} catch {
|
|
24926
|
+
throw new Error(`Failed to parse reports response: ${data}`);
|
|
24927
|
+
}
|
|
24928
|
+
} else {
|
|
24929
|
+
throw new Error(formatHttpError("Failed to fetch reports", response.status, data));
|
|
24930
|
+
}
|
|
24931
|
+
}
|
|
24932
|
+
var MAX_ALL_REPORTS2 = 1e4;
|
|
24933
|
+
async function fetchAllReports2(params) {
|
|
24934
|
+
const pageSize = params.limit ?? 100;
|
|
24935
|
+
const all = [];
|
|
24936
|
+
let beforeId;
|
|
24937
|
+
while (true) {
|
|
24938
|
+
const page = await fetchReports2({ ...params, limit: pageSize, beforeId });
|
|
24939
|
+
if (page.length === 0)
|
|
24940
|
+
break;
|
|
24941
|
+
all.push(...page);
|
|
24942
|
+
if (all.length >= MAX_ALL_REPORTS2) {
|
|
24943
|
+
console.warn(`Warning: reached maximum of ${MAX_ALL_REPORTS2} reports, stopping pagination`);
|
|
24944
|
+
break;
|
|
24945
|
+
}
|
|
24946
|
+
beforeId = page[page.length - 1].id;
|
|
24947
|
+
if (page.length < pageSize)
|
|
24948
|
+
break;
|
|
24949
|
+
}
|
|
24950
|
+
return all;
|
|
24951
|
+
}
|
|
24952
|
+
async function fetchReportFiles2(params) {
|
|
24953
|
+
const { apiKey, apiBaseUrl, reportId, type: type2, checkId, debug } = params;
|
|
24954
|
+
if (!apiKey) {
|
|
24955
|
+
throw new Error("API key is required");
|
|
24956
|
+
}
|
|
24957
|
+
if (reportId === undefined && !checkId) {
|
|
24958
|
+
throw new Error("Either reportId or checkId is required");
|
|
24959
|
+
}
|
|
24960
|
+
const base = normalizeBaseUrl(apiBaseUrl);
|
|
24961
|
+
const url = new URL(`${base}/checkup_report_files`);
|
|
24962
|
+
if (typeof reportId === "number") {
|
|
24963
|
+
url.searchParams.set("checkup_report_id", `eq.${reportId}`);
|
|
24964
|
+
}
|
|
24965
|
+
url.searchParams.set("order", "id.asc");
|
|
24966
|
+
if (type2) {
|
|
24967
|
+
url.searchParams.set("type", `eq.${type2}`);
|
|
24968
|
+
}
|
|
24969
|
+
if (checkId) {
|
|
24970
|
+
url.searchParams.set("check_id", `eq.${checkId}`);
|
|
24971
|
+
}
|
|
24972
|
+
const headers = {
|
|
24973
|
+
"access-token": apiKey,
|
|
24974
|
+
Prefer: "return=representation",
|
|
24975
|
+
"Content-Type": "application/json",
|
|
24976
|
+
Connection: "close"
|
|
24977
|
+
};
|
|
24978
|
+
if (debug) {
|
|
24979
|
+
const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
|
|
24980
|
+
console.log(`Debug: Resolved API base URL: ${base}`);
|
|
24981
|
+
console.log(`Debug: GET URL: ${url.toString()}`);
|
|
24982
|
+
console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
|
|
24983
|
+
}
|
|
24984
|
+
const response = await fetch(url.toString(), { method: "GET", headers });
|
|
24985
|
+
if (debug) {
|
|
24986
|
+
console.log(`Debug: Response status: ${response.status}`);
|
|
24987
|
+
}
|
|
24988
|
+
const data = await response.text();
|
|
24989
|
+
if (response.ok) {
|
|
24990
|
+
try {
|
|
24991
|
+
return JSON.parse(data);
|
|
24992
|
+
} catch {
|
|
24993
|
+
throw new Error(`Failed to parse report files response: ${data}`);
|
|
24994
|
+
}
|
|
24995
|
+
} else {
|
|
24996
|
+
throw new Error(formatHttpError("Failed to fetch report files", response.status, data));
|
|
24997
|
+
}
|
|
24998
|
+
}
|
|
24999
|
+
async function fetchReportFileData2(params) {
|
|
25000
|
+
const { apiKey, apiBaseUrl, reportId, type: type2, checkId, debug } = params;
|
|
25001
|
+
if (!apiKey) {
|
|
25002
|
+
throw new Error("API key is required");
|
|
25003
|
+
}
|
|
25004
|
+
if (reportId === undefined && !checkId) {
|
|
25005
|
+
throw new Error("Either reportId or checkId is required");
|
|
25006
|
+
}
|
|
25007
|
+
const base = normalizeBaseUrl(apiBaseUrl);
|
|
25008
|
+
const url = new URL(`${base}/checkup_report_file_data`);
|
|
25009
|
+
if (typeof reportId === "number") {
|
|
25010
|
+
url.searchParams.set("checkup_report_id", `eq.${reportId}`);
|
|
25011
|
+
}
|
|
25012
|
+
url.searchParams.set("order", "id.asc");
|
|
25013
|
+
if (type2) {
|
|
25014
|
+
url.searchParams.set("type", `eq.${type2}`);
|
|
25015
|
+
}
|
|
25016
|
+
if (checkId) {
|
|
25017
|
+
url.searchParams.set("check_id", `eq.${checkId}`);
|
|
25018
|
+
}
|
|
25019
|
+
const headers = {
|
|
25020
|
+
"access-token": apiKey,
|
|
25021
|
+
Prefer: "return=representation",
|
|
25022
|
+
"Content-Type": "application/json",
|
|
25023
|
+
Connection: "close"
|
|
25024
|
+
};
|
|
25025
|
+
if (debug) {
|
|
25026
|
+
const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
|
|
25027
|
+
console.log(`Debug: Resolved API base URL: ${base}`);
|
|
25028
|
+
console.log(`Debug: GET URL: ${url.toString()}`);
|
|
25029
|
+
console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
|
|
25030
|
+
}
|
|
25031
|
+
const response = await fetch(url.toString(), { method: "GET", headers });
|
|
25032
|
+
if (debug) {
|
|
25033
|
+
console.log(`Debug: Response status: ${response.status}`);
|
|
25034
|
+
}
|
|
25035
|
+
const data = await response.text();
|
|
25036
|
+
if (response.ok) {
|
|
25037
|
+
try {
|
|
25038
|
+
return JSON.parse(data);
|
|
25039
|
+
} catch {
|
|
25040
|
+
throw new Error(`Failed to parse report file data response: ${data}`);
|
|
25041
|
+
}
|
|
25042
|
+
} else {
|
|
25043
|
+
throw new Error(formatHttpError("Failed to fetch report file data", response.status, data));
|
|
25044
|
+
}
|
|
25045
|
+
}
|
|
25046
|
+
function renderMarkdownForTerminal(md) {
|
|
25047
|
+
if (!md)
|
|
25048
|
+
return "";
|
|
25049
|
+
const RESET = "\x1B[0m";
|
|
25050
|
+
const BOLD = "\x1B[1m";
|
|
25051
|
+
const BOLD_UNDERLINE = "\x1B[1;4m";
|
|
25052
|
+
const DIM = "\x1B[2m";
|
|
25053
|
+
const ITALIC = "\x1B[3m";
|
|
25054
|
+
const CYAN = "\x1B[36m";
|
|
25055
|
+
const lines = md.split(`
|
|
25056
|
+
`);
|
|
25057
|
+
const output = [];
|
|
25058
|
+
let inCodeBlock = false;
|
|
25059
|
+
for (const line of lines) {
|
|
25060
|
+
if (line.trimStart().startsWith("```")) {
|
|
25061
|
+
inCodeBlock = !inCodeBlock;
|
|
25062
|
+
if (inCodeBlock) {
|
|
25063
|
+
output.push(`${DIM}${"─".repeat(40)}${RESET}`);
|
|
25064
|
+
} else {
|
|
25065
|
+
output.push(`${DIM}${"─".repeat(40)}${RESET}`);
|
|
25066
|
+
}
|
|
25067
|
+
continue;
|
|
25068
|
+
}
|
|
25069
|
+
if (inCodeBlock) {
|
|
25070
|
+
output.push(`${DIM} ${line}${RESET}`);
|
|
25071
|
+
continue;
|
|
25072
|
+
}
|
|
25073
|
+
if (/^-{3,}$/.test(line.trim()) || /^\*{3,}$/.test(line.trim()) || /^_{3,}$/.test(line.trim())) {
|
|
25074
|
+
output.push(`${DIM}${"─".repeat(60)}${RESET}`);
|
|
25075
|
+
continue;
|
|
25076
|
+
}
|
|
25077
|
+
const headingMatch = line.match(/^(#{1,6})\s+(.*)/);
|
|
25078
|
+
if (headingMatch) {
|
|
25079
|
+
const level = headingMatch[1].length;
|
|
25080
|
+
const text = headingMatch[2];
|
|
25081
|
+
if (level === 1) {
|
|
25082
|
+
output.push(`${BOLD_UNDERLINE}${text}${RESET}`);
|
|
25083
|
+
} else {
|
|
25084
|
+
output.push(`${BOLD}${text}${RESET}`);
|
|
25085
|
+
}
|
|
25086
|
+
continue;
|
|
25087
|
+
}
|
|
25088
|
+
let formatted = line;
|
|
25089
|
+
formatted = formatted.replace(/\*\*(.+?)\*\*/g, `${BOLD}$1${RESET}`);
|
|
25090
|
+
formatted = formatted.replace(/__(.+?)__/g, `${BOLD}$1${RESET}`);
|
|
25091
|
+
formatted = formatted.replace(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, `${ITALIC}$1${RESET}`);
|
|
25092
|
+
formatted = formatted.replace(/(?<=^|[\s(])_([^\s_](?:.*?[^\s_])?)_(?=$|[\s),.:;!?])/g, `${ITALIC}$1${RESET}`);
|
|
25093
|
+
formatted = formatted.replace(/`([^`]+)`/g, `${CYAN}$1${RESET}`);
|
|
25094
|
+
output.push(formatted);
|
|
25095
|
+
}
|
|
25096
|
+
return output.join(`
|
|
25097
|
+
`);
|
|
25098
|
+
}
|
|
25099
|
+
|
|
24591
25100
|
// lib/util.ts
|
|
24592
25101
|
function maskSecret2(secret) {
|
|
24593
25102
|
if (!secret)
|
|
@@ -32081,6 +32590,193 @@ issues.command("update-action-item <actionItemId>").description("update an actio
|
|
|
32081
32590
|
process.exitCode = 1;
|
|
32082
32591
|
}
|
|
32083
32592
|
});
|
|
32593
|
+
var reports = program2.command("reports").description("checkup reports management");
|
|
32594
|
+
reports.command("list").description("list checkup reports").option("--project-id <id>", "filter by project id", (v) => parseInt(v, 10)).option("--status <status>", "filter by status (e.g., completed)").option("--limit <n>", "max number of reports to return (default: 20)", (v) => parseInt(v, 10)).option("--before <date>", "show reports created before this date (YYYY-MM-DD, DD.MM.YYYY, etc.)").option("--all", "fetch all reports (paginated automatically)").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (opts) => {
|
|
32595
|
+
const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Fetching reports...");
|
|
32596
|
+
try {
|
|
32597
|
+
const rootOpts = program2.opts();
|
|
32598
|
+
const cfg = readConfig();
|
|
32599
|
+
const { apiKey } = getConfig(rootOpts);
|
|
32600
|
+
if (!apiKey) {
|
|
32601
|
+
spinner.stop();
|
|
32602
|
+
console.error("API key is required. Run 'pgai auth' first or set --api-key.");
|
|
32603
|
+
process.exitCode = 1;
|
|
32604
|
+
return;
|
|
32605
|
+
}
|
|
32606
|
+
if (opts.all && opts.before) {
|
|
32607
|
+
spinner.stop();
|
|
32608
|
+
console.error("--all and --before cannot be used together");
|
|
32609
|
+
process.exitCode = 1;
|
|
32610
|
+
return;
|
|
32611
|
+
}
|
|
32612
|
+
const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
|
|
32613
|
+
let result;
|
|
32614
|
+
if (opts.all) {
|
|
32615
|
+
result = await fetchAllReports2({
|
|
32616
|
+
apiKey,
|
|
32617
|
+
apiBaseUrl,
|
|
32618
|
+
projectId: opts.projectId,
|
|
32619
|
+
status: opts.status,
|
|
32620
|
+
limit: opts.limit,
|
|
32621
|
+
debug: !!opts.debug
|
|
32622
|
+
});
|
|
32623
|
+
} else {
|
|
32624
|
+
result = await fetchReports2({
|
|
32625
|
+
apiKey,
|
|
32626
|
+
apiBaseUrl,
|
|
32627
|
+
projectId: opts.projectId,
|
|
32628
|
+
status: opts.status,
|
|
32629
|
+
limit: opts.limit,
|
|
32630
|
+
beforeDate: opts.before ? parseFlexibleDate2(opts.before) : undefined,
|
|
32631
|
+
debug: !!opts.debug
|
|
32632
|
+
});
|
|
32633
|
+
}
|
|
32634
|
+
spinner.stop();
|
|
32635
|
+
printResult(result, opts.json);
|
|
32636
|
+
} catch (err) {
|
|
32637
|
+
spinner.stop();
|
|
32638
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
32639
|
+
console.error(message);
|
|
32640
|
+
process.exitCode = 1;
|
|
32641
|
+
}
|
|
32642
|
+
});
|
|
32643
|
+
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)").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (reportId, opts) => {
|
|
32644
|
+
const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Fetching report files...");
|
|
32645
|
+
try {
|
|
32646
|
+
const rootOpts = program2.opts();
|
|
32647
|
+
const cfg = readConfig();
|
|
32648
|
+
const { apiKey } = getConfig(rootOpts);
|
|
32649
|
+
if (!apiKey) {
|
|
32650
|
+
spinner.stop();
|
|
32651
|
+
console.error("API key is required. Run 'pgai auth' first or set --api-key.");
|
|
32652
|
+
process.exitCode = 1;
|
|
32653
|
+
return;
|
|
32654
|
+
}
|
|
32655
|
+
let numericId;
|
|
32656
|
+
if (reportId !== undefined) {
|
|
32657
|
+
numericId = parseInt(reportId, 10);
|
|
32658
|
+
if (isNaN(numericId)) {
|
|
32659
|
+
spinner.stop();
|
|
32660
|
+
console.error("reportId must be a number");
|
|
32661
|
+
process.exitCode = 1;
|
|
32662
|
+
return;
|
|
32663
|
+
}
|
|
32664
|
+
}
|
|
32665
|
+
if (numericId === undefined && !opts.checkId) {
|
|
32666
|
+
spinner.stop();
|
|
32667
|
+
console.error("Either reportId or --check-id is required");
|
|
32668
|
+
process.exitCode = 1;
|
|
32669
|
+
return;
|
|
32670
|
+
}
|
|
32671
|
+
const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
|
|
32672
|
+
const result = await fetchReportFiles2({
|
|
32673
|
+
apiKey,
|
|
32674
|
+
apiBaseUrl,
|
|
32675
|
+
reportId: numericId,
|
|
32676
|
+
type: opts.type,
|
|
32677
|
+
checkId: opts.checkId,
|
|
32678
|
+
debug: !!opts.debug
|
|
32679
|
+
});
|
|
32680
|
+
spinner.stop();
|
|
32681
|
+
printResult(result, opts.json);
|
|
32682
|
+
} catch (err) {
|
|
32683
|
+
spinner.stop();
|
|
32684
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
32685
|
+
console.error(message);
|
|
32686
|
+
process.exitCode = 1;
|
|
32687
|
+
}
|
|
32688
|
+
});
|
|
32689
|
+
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)").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (reportId, opts) => {
|
|
32690
|
+
const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Fetching report data...");
|
|
32691
|
+
try {
|
|
32692
|
+
const rootOpts = program2.opts();
|
|
32693
|
+
const cfg = readConfig();
|
|
32694
|
+
const { apiKey } = getConfig(rootOpts);
|
|
32695
|
+
if (!apiKey) {
|
|
32696
|
+
spinner.stop();
|
|
32697
|
+
console.error("API key is required. Run 'pgai auth' first or set --api-key.");
|
|
32698
|
+
process.exitCode = 1;
|
|
32699
|
+
return;
|
|
32700
|
+
}
|
|
32701
|
+
let numericId;
|
|
32702
|
+
if (reportId !== undefined) {
|
|
32703
|
+
numericId = parseInt(reportId, 10);
|
|
32704
|
+
if (isNaN(numericId)) {
|
|
32705
|
+
spinner.stop();
|
|
32706
|
+
console.error("reportId must be a number");
|
|
32707
|
+
process.exitCode = 1;
|
|
32708
|
+
return;
|
|
32709
|
+
}
|
|
32710
|
+
}
|
|
32711
|
+
if (numericId === undefined && !opts.checkId) {
|
|
32712
|
+
spinner.stop();
|
|
32713
|
+
console.error("Either reportId or --check-id is required");
|
|
32714
|
+
process.exitCode = 1;
|
|
32715
|
+
return;
|
|
32716
|
+
}
|
|
32717
|
+
const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
|
|
32718
|
+
const effectiveType = opts.type ?? (!opts.json && !opts.output ? "md" : undefined);
|
|
32719
|
+
const result = await fetchReportFileData2({
|
|
32720
|
+
apiKey,
|
|
32721
|
+
apiBaseUrl,
|
|
32722
|
+
reportId: numericId,
|
|
32723
|
+
type: effectiveType,
|
|
32724
|
+
checkId: opts.checkId,
|
|
32725
|
+
debug: !!opts.debug
|
|
32726
|
+
});
|
|
32727
|
+
spinner.stop();
|
|
32728
|
+
if (opts.output) {
|
|
32729
|
+
const dir = path5.resolve(opts.output);
|
|
32730
|
+
fs5.mkdirSync(dir, { recursive: true });
|
|
32731
|
+
for (const f of result) {
|
|
32732
|
+
const safeName = path5.basename(f.filename);
|
|
32733
|
+
const filePath = path5.join(dir, safeName);
|
|
32734
|
+
const content = f.type === "json" ? JSON.stringify(tryParseJson(f.data), null, 2) : f.data;
|
|
32735
|
+
fs5.writeFileSync(filePath, content, "utf-8");
|
|
32736
|
+
console.log(filePath);
|
|
32737
|
+
}
|
|
32738
|
+
} else if (opts.json) {
|
|
32739
|
+
const processed = result.map((f) => ({
|
|
32740
|
+
...f,
|
|
32741
|
+
data: f.type === "json" ? tryParseJson(f.data) : f.data
|
|
32742
|
+
}));
|
|
32743
|
+
printResult(processed, true);
|
|
32744
|
+
} else if (opts.formatted && process.stdout.isTTY) {
|
|
32745
|
+
for (const f of result) {
|
|
32746
|
+
if (result.length > 1) {
|
|
32747
|
+
console.log(`\x1B[1m--- ${f.filename} (${f.check_id}, ${f.type}) ---\x1B[0m`);
|
|
32748
|
+
}
|
|
32749
|
+
if (f.type === "md") {
|
|
32750
|
+
console.log(renderMarkdownForTerminal(f.data));
|
|
32751
|
+
} else if (f.type === "json") {
|
|
32752
|
+
const parsed = tryParseJson(f.data);
|
|
32753
|
+
console.log(typeof parsed === "string" ? parsed : JSON.stringify(parsed, null, 2));
|
|
32754
|
+
} else {
|
|
32755
|
+
console.log(f.data);
|
|
32756
|
+
}
|
|
32757
|
+
}
|
|
32758
|
+
} else {
|
|
32759
|
+
for (const f of result) {
|
|
32760
|
+
if (result.length > 1) {
|
|
32761
|
+
console.log(`--- ${f.filename} (${f.check_id}, ${f.type}) ---`);
|
|
32762
|
+
}
|
|
32763
|
+
console.log(f.data);
|
|
32764
|
+
}
|
|
32765
|
+
}
|
|
32766
|
+
} catch (err) {
|
|
32767
|
+
spinner.stop();
|
|
32768
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
32769
|
+
console.error(message);
|
|
32770
|
+
process.exitCode = 1;
|
|
32771
|
+
}
|
|
32772
|
+
});
|
|
32773
|
+
function tryParseJson(s) {
|
|
32774
|
+
try {
|
|
32775
|
+
return JSON.parse(s);
|
|
32776
|
+
} catch {
|
|
32777
|
+
return s;
|
|
32778
|
+
}
|
|
32779
|
+
}
|
|
32084
32780
|
var mcp = program2.command("mcp").description("MCP server integration");
|
|
32085
32781
|
mcp.command("start").description("start MCP stdio server").option("--debug", "enable debug output").action(async (opts) => {
|
|
32086
32782
|
const rootOpts = program2.opts();
|