@chainpatrol/cli 0.1.0
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/README.md +224 -0
- package/dist/breakdown-JVN66HY3.js +69 -0
- package/dist/chunk-D2QGXYXZ.js +126 -0
- package/dist/chunk-E2LAMILJ.js +48 -0
- package/dist/chunk-EEG7T6WT.js +287 -0
- package/dist/chunk-H7UKKLCV.js +6572 -0
- package/dist/chunk-IUZB3DQW.js +237 -0
- package/dist/chunk-JCMWDZYY.js +39 -0
- package/dist/chunk-U73SABXK.js +46 -0
- package/dist/chunk-VFT3TD3E.js +82 -0
- package/dist/cli.js +895 -0
- package/dist/completions-EGQIARFC.js +12 -0
- package/dist/config-Z3TASRME.js +10 -0
- package/dist/configs-update-BK2S6AZ6.js +101 -0
- package/dist/create-4SQUBQI7.js +128 -0
- package/dist/drift-VRZKQC4P.js +80 -0
- package/dist/found-4O3AISNI.js +93 -0
- package/dist/healthcheck-7DR5MGEQ.js +94 -0
- package/dist/list-6L7XR4SZ.js +94 -0
- package/dist/list-HZAHEHDM.js +40 -0
- package/dist/list-IBMM562A.js +139 -0
- package/dist/list-json-TPBLJBD3.js +24 -0
- package/dist/login-G7LPHKDR.js +162 -0
- package/dist/login-json-LKB72OFY.js +71 -0
- package/dist/logout-LA7VEKON.js +25 -0
- package/dist/logout-json-4GIJZJ46.js +18 -0
- package/dist/run-PABQKATZ.js +112 -0
- package/dist/run-U62KVNTH.js +34 -0
- package/dist/setup-skill-U24CJZ6T.js +211 -0
- package/dist/snapshot-JEVDTE74.js +88 -0
- package/dist/summary-YG5NYIOA.js +61 -0
- package/dist/validate-PI7GPT5I.js +79 -0
- package/package.json +50 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CliExitError,
|
|
3
|
+
ExitCode
|
|
4
|
+
} from "./chunk-E2LAMILJ.js";
|
|
5
|
+
import {
|
|
6
|
+
printOutput
|
|
7
|
+
} from "./chunk-VFT3TD3E.js";
|
|
8
|
+
import {
|
|
9
|
+
createApiClient
|
|
10
|
+
} from "./chunk-H7UKKLCV.js";
|
|
11
|
+
import "./chunk-EEG7T6WT.js";
|
|
12
|
+
import "./chunk-U73SABXK.js";
|
|
13
|
+
|
|
14
|
+
// src/commands/detections/configs-update.ts
|
|
15
|
+
async function runDetectionsConfigsUpdate(options) {
|
|
16
|
+
if (options.enable && options.disable) {
|
|
17
|
+
throw new CliExitError("Use either --enable or --disable, not both.", ExitCode.USAGE);
|
|
18
|
+
}
|
|
19
|
+
const status = options.enable ? "ENABLED" : options.disable ? "DISABLED" : void 0;
|
|
20
|
+
const outputFormat = options.outputFormat ?? (options.json ? "json" : "human");
|
|
21
|
+
const payload = {
|
|
22
|
+
slug: options.org,
|
|
23
|
+
configId: options.configId,
|
|
24
|
+
status,
|
|
25
|
+
title: options.title,
|
|
26
|
+
description: options.description,
|
|
27
|
+
cron: options.cron,
|
|
28
|
+
config: options.configPatch,
|
|
29
|
+
mergeConfig: options.mergeConfig ?? true
|
|
30
|
+
};
|
|
31
|
+
if (options.dryRun) {
|
|
32
|
+
printOutput({
|
|
33
|
+
outputFormat,
|
|
34
|
+
json: {
|
|
35
|
+
dryRun: true,
|
|
36
|
+
mutation: "detection.configs.update",
|
|
37
|
+
payload,
|
|
38
|
+
explanation: options.explain ? "This command updates status/metadata/config values on a detection config." : void 0
|
|
39
|
+
},
|
|
40
|
+
markdown: [
|
|
41
|
+
"# Dry Run: Detection Config Update",
|
|
42
|
+
"",
|
|
43
|
+
`- Org: ${payload.slug}`,
|
|
44
|
+
`- Config ID: ${payload.configId}`,
|
|
45
|
+
`- Status update: ${payload.status ?? "none"}`
|
|
46
|
+
].join("\n"),
|
|
47
|
+
human: () => {
|
|
48
|
+
console.log("Dry run only. No config changes were saved.");
|
|
49
|
+
console.log(
|
|
50
|
+
`Would update config ${payload.configId} for org=${payload.slug}${payload.status ? ` status=${payload.status}` : ""}`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const client = options.apiClient ?? createApiClient();
|
|
57
|
+
const result = await client.updateDetectionConfig(payload);
|
|
58
|
+
printOutput({
|
|
59
|
+
outputFormat,
|
|
60
|
+
json: {
|
|
61
|
+
...result,
|
|
62
|
+
explanation: options.explain ? "Applied mutation to update detection config metadata and/or configuration payload." : void 0
|
|
63
|
+
},
|
|
64
|
+
markdown: [
|
|
65
|
+
`# Detection Config Updated (${options.org})`,
|
|
66
|
+
"",
|
|
67
|
+
`- Config: ${result.config.id}`,
|
|
68
|
+
`- Source: ${result.config.source}`,
|
|
69
|
+
`- Status: ${result.config.status}`,
|
|
70
|
+
`- Updated at: ${result.config.updatedAt}`
|
|
71
|
+
].join("\n"),
|
|
72
|
+
human: () => {
|
|
73
|
+
console.log(
|
|
74
|
+
`Updated config ${result.config.id} [${result.config.source}] status=${result.config.status}`
|
|
75
|
+
);
|
|
76
|
+
if (result.config.title) {
|
|
77
|
+
console.log(`Title: ${result.config.title}`);
|
|
78
|
+
}
|
|
79
|
+
if (result.config.cron) {
|
|
80
|
+
console.log(`Cron: ${result.config.cron}`);
|
|
81
|
+
}
|
|
82
|
+
if (Object.keys(result.config.config).length > 0) {
|
|
83
|
+
console.log(`Config keys: ${Object.keys(result.config.config).join(", ")}`);
|
|
84
|
+
}
|
|
85
|
+
if (options.explain) {
|
|
86
|
+
console.log(
|
|
87
|
+
`Changed fields: ${[
|
|
88
|
+
status ? "status" : null,
|
|
89
|
+
options.title !== void 0 ? "title" : null,
|
|
90
|
+
options.description !== void 0 ? "description" : null,
|
|
91
|
+
options.cron !== void 0 ? "cron" : null,
|
|
92
|
+
options.configPatch ? "config" : null
|
|
93
|
+
].filter(Boolean).join(", ") || "none"}`
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
export {
|
|
100
|
+
runDetectionsConfigsUpdate
|
|
101
|
+
};
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CliExitError,
|
|
3
|
+
ExitCode
|
|
4
|
+
} from "./chunk-E2LAMILJ.js";
|
|
5
|
+
import {
|
|
6
|
+
printOutput,
|
|
7
|
+
toCsvRows
|
|
8
|
+
} from "./chunk-VFT3TD3E.js";
|
|
9
|
+
import {
|
|
10
|
+
createApiClient
|
|
11
|
+
} from "./chunk-H7UKKLCV.js";
|
|
12
|
+
import "./chunk-EEG7T6WT.js";
|
|
13
|
+
import "./chunk-U73SABXK.js";
|
|
14
|
+
|
|
15
|
+
// src/commands/reports/create.ts
|
|
16
|
+
import { readFileSync } from "fs";
|
|
17
|
+
function parseAssetStatus(value) {
|
|
18
|
+
const normalized = value.trim().toUpperCase();
|
|
19
|
+
if (normalized === "BLOCKED" || normalized === "ALLOWED" || normalized === "UNKNOWN") {
|
|
20
|
+
return normalized;
|
|
21
|
+
}
|
|
22
|
+
throw new Error(`Invalid asset status '${value}'. Use BLOCKED, ALLOWED, or UNKNOWN.`);
|
|
23
|
+
}
|
|
24
|
+
var ASSET_STATUS_SUFFIX = /:(BLOCKED|ALLOWED|UNKNOWN)$/i;
|
|
25
|
+
function parseAssetValue(value) {
|
|
26
|
+
const trimmed = value.trim();
|
|
27
|
+
const match = trimmed.match(ASSET_STATUS_SUFFIX);
|
|
28
|
+
if (!match) {
|
|
29
|
+
return { content: trimmed, status: "BLOCKED" };
|
|
30
|
+
}
|
|
31
|
+
const content = trimmed.slice(0, match.index).trim();
|
|
32
|
+
if (!content) {
|
|
33
|
+
throw new Error(`Invalid --asset value '${value}'.`);
|
|
34
|
+
}
|
|
35
|
+
return { content, status: parseAssetStatus(match[1]) };
|
|
36
|
+
}
|
|
37
|
+
function readPayloadFile(payloadFile) {
|
|
38
|
+
const content = readFileSync(payloadFile, "utf-8");
|
|
39
|
+
const parsed = JSON.parse(content);
|
|
40
|
+
return parsed;
|
|
41
|
+
}
|
|
42
|
+
async function runReportsCreate(options) {
|
|
43
|
+
const filePayload = options.payloadFile ? readPayloadFile(options.payloadFile) : void 0;
|
|
44
|
+
const cliAssets = options.assets?.map(parseAssetValue) ?? [];
|
|
45
|
+
const payload = {
|
|
46
|
+
...filePayload ?? {},
|
|
47
|
+
organizationSlug: options.org ?? filePayload?.organizationSlug,
|
|
48
|
+
title: options.title ?? filePayload?.title,
|
|
49
|
+
description: options.description ?? filePayload?.description,
|
|
50
|
+
contactInfo: options.contactInfo ?? filePayload?.contactInfo,
|
|
51
|
+
attachmentUrls: options.attachmentUrls ?? filePayload?.attachmentUrls,
|
|
52
|
+
externalSubmissionLink: options.externalSubmissionLink ?? filePayload?.externalSubmissionLink,
|
|
53
|
+
userAgent: options.userAgent ?? filePayload?.userAgent,
|
|
54
|
+
referrer: options.referrer ?? filePayload?.referrer,
|
|
55
|
+
assets: cliAssets.length > 0 ? cliAssets : filePayload?.assets ?? [],
|
|
56
|
+
rawAssetsInput: filePayload?.rawAssetsInput,
|
|
57
|
+
externalReporter: filePayload?.externalReporter
|
|
58
|
+
};
|
|
59
|
+
if (!payload.assets || payload.assets.length === 0) {
|
|
60
|
+
throw new CliExitError(
|
|
61
|
+
"At least one asset is required. Use --asset content[:status] or provide assets in --payload-file.",
|
|
62
|
+
ExitCode.USAGE
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
const outputFormat = options.outputFormat ?? (options.json ? "json" : "human");
|
|
66
|
+
if (options.dryRun) {
|
|
67
|
+
printOutput({
|
|
68
|
+
outputFormat,
|
|
69
|
+
json: {
|
|
70
|
+
dryRun: true,
|
|
71
|
+
mutation: "reports.create",
|
|
72
|
+
payload,
|
|
73
|
+
explanation: options.explain ? "This command creates a report and proposal assets for triage." : void 0
|
|
74
|
+
},
|
|
75
|
+
markdown: [
|
|
76
|
+
"# Dry Run: Report Create",
|
|
77
|
+
"",
|
|
78
|
+
`- Org: ${payload.organizationSlug ?? "from token context"}`,
|
|
79
|
+
`- Title: ${payload.title ?? "(none)"}`,
|
|
80
|
+
`- Assets: ${payload.assets.length}`
|
|
81
|
+
].join("\n"),
|
|
82
|
+
csv: toCsvRows(
|
|
83
|
+
payload.assets.map((asset) => ({
|
|
84
|
+
content: asset.content,
|
|
85
|
+
status: asset.status ?? "BLOCKED"
|
|
86
|
+
}))
|
|
87
|
+
),
|
|
88
|
+
human: () => {
|
|
89
|
+
console.log("Dry run only. No report was created.");
|
|
90
|
+
console.log(
|
|
91
|
+
`Would create report for org=${payload.organizationSlug ?? "default"} with ${payload.assets.length} asset(s).`
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const client = options.apiClient ?? createApiClient();
|
|
98
|
+
const result = await client.createReport(payload);
|
|
99
|
+
printOutput({
|
|
100
|
+
outputFormat,
|
|
101
|
+
json: {
|
|
102
|
+
...result,
|
|
103
|
+
explanation: options.explain ? "Report creation succeeds after payload validation and report service processing." : void 0
|
|
104
|
+
},
|
|
105
|
+
markdown: [
|
|
106
|
+
"# Report Created",
|
|
107
|
+
"",
|
|
108
|
+
`- Report ID: ${result.id}`,
|
|
109
|
+
`- Organization: ${result.organization?.slug ?? "unknown"}`,
|
|
110
|
+
`- Created at: ${result.createdAt}`
|
|
111
|
+
].join("\n"),
|
|
112
|
+
human: () => {
|
|
113
|
+
console.log(`Created report ${result.id}`);
|
|
114
|
+
if (result.organization) {
|
|
115
|
+
console.log(`Organization: ${result.organization.slug}`);
|
|
116
|
+
}
|
|
117
|
+
console.log(`Created at: ${result.createdAt}`);
|
|
118
|
+
if (options.explain) {
|
|
119
|
+
console.log(
|
|
120
|
+
`Submitted ${payload.assets.length} asset(s) for report triage and classification.`
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
export {
|
|
127
|
+
runReportsCreate
|
|
128
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CliExitError,
|
|
3
|
+
ExitCode
|
|
4
|
+
} from "./chunk-E2LAMILJ.js";
|
|
5
|
+
import {
|
|
6
|
+
printOutput,
|
|
7
|
+
toCsvRows
|
|
8
|
+
} from "./chunk-VFT3TD3E.js";
|
|
9
|
+
import {
|
|
10
|
+
createApiClient
|
|
11
|
+
} from "./chunk-H7UKKLCV.js";
|
|
12
|
+
import "./chunk-EEG7T6WT.js";
|
|
13
|
+
import "./chunk-U73SABXK.js";
|
|
14
|
+
|
|
15
|
+
// src/commands/detections/drift.ts
|
|
16
|
+
async function runDetectionsDrift(options) {
|
|
17
|
+
const client = options.apiClient ?? createApiClient();
|
|
18
|
+
const outputFormat = options.outputFormat ?? (options.json ? "json" : "human");
|
|
19
|
+
const result = await client.getDetectionDrift({
|
|
20
|
+
slug: options.org,
|
|
21
|
+
source: options.source,
|
|
22
|
+
configIds: options.configId ? [options.configId] : void 0,
|
|
23
|
+
includeDisabled: options.includeDisabled ?? false,
|
|
24
|
+
lookbackHours: options.lookbackHours
|
|
25
|
+
});
|
|
26
|
+
printOutput({
|
|
27
|
+
outputFormat,
|
|
28
|
+
json: {
|
|
29
|
+
...result,
|
|
30
|
+
explanation: options.explain ? {
|
|
31
|
+
signalTypes: ["zero_results_too_long", "noisy_source", "stale_query"],
|
|
32
|
+
failureCondition: "summary.signalCount > 0"
|
|
33
|
+
} : void 0
|
|
34
|
+
},
|
|
35
|
+
markdown: [
|
|
36
|
+
`# Detection Drift (${options.org})`,
|
|
37
|
+
"",
|
|
38
|
+
`- Checked configs: ${result.summary.checkedConfigs}`,
|
|
39
|
+
`- Signals: ${result.summary.signalCount}`,
|
|
40
|
+
`- Zero results too long: ${result.summary.zeroResultsCount}`,
|
|
41
|
+
`- Noisy source: ${result.summary.noisyCount}`,
|
|
42
|
+
`- Stale query: ${result.summary.staleCount}`,
|
|
43
|
+
"",
|
|
44
|
+
...result.signals.map(
|
|
45
|
+
(signal) => `- ${signal.severity.toUpperCase()} ${signal.signal} [${signal.source}] #${signal.configId}`
|
|
46
|
+
)
|
|
47
|
+
].join("\n"),
|
|
48
|
+
csv: toCsvRows(
|
|
49
|
+
result.signals.map((signal) => ({
|
|
50
|
+
signal: signal.signal,
|
|
51
|
+
severity: signal.severity,
|
|
52
|
+
configId: signal.configId,
|
|
53
|
+
source: signal.source,
|
|
54
|
+
title: signal.title,
|
|
55
|
+
status: signal.status
|
|
56
|
+
}))
|
|
57
|
+
),
|
|
58
|
+
human: () => {
|
|
59
|
+
console.log(
|
|
60
|
+
`Detection drift for ${options.org}: ${result.summary.signalCount} signal(s) across ${result.summary.checkedConfigs} config(s)`
|
|
61
|
+
);
|
|
62
|
+
for (const signal of result.signals) {
|
|
63
|
+
console.log(
|
|
64
|
+
`- ${signal.severity.toUpperCase()} ${signal.signal} [${signal.source}] config ${signal.configId}`
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
if (options.explain) {
|
|
68
|
+
console.log(
|
|
69
|
+
"Signals are based on zero results over threshold, noisy result/allow ratios, and stale query updates."
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
if (result.summary.signalCount > 0) {
|
|
75
|
+
throw new CliExitError("Detection drift signals found.", ExitCode.CHECK_FAILED);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
export {
|
|
79
|
+
runDetectionsDrift
|
|
80
|
+
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import {
|
|
2
|
+
printOutput,
|
|
3
|
+
toCsvRows
|
|
4
|
+
} from "./chunk-VFT3TD3E.js";
|
|
5
|
+
import {
|
|
6
|
+
DateTime,
|
|
7
|
+
createApiClient
|
|
8
|
+
} from "./chunk-H7UKKLCV.js";
|
|
9
|
+
import "./chunk-EEG7T6WT.js";
|
|
10
|
+
import "./chunk-U73SABXK.js";
|
|
11
|
+
|
|
12
|
+
// src/lib/date-range.ts
|
|
13
|
+
function resolveDateRange({
|
|
14
|
+
from,
|
|
15
|
+
to,
|
|
16
|
+
thisWeek
|
|
17
|
+
}) {
|
|
18
|
+
if (from && to) {
|
|
19
|
+
return {
|
|
20
|
+
startDate: DateTime.fromISO(from).toUTC().toISO() ?? from,
|
|
21
|
+
endDate: DateTime.fromISO(to).toUTC().toISO() ?? to
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
if (from && !to) {
|
|
25
|
+
const now2 = DateTime.now().toUTC();
|
|
26
|
+
return {
|
|
27
|
+
startDate: DateTime.fromISO(from).toUTC().toISO() ?? from,
|
|
28
|
+
endDate: now2.toISO() ?? ""
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
if (!from && to) {
|
|
32
|
+
const end = DateTime.fromISO(to).toUTC();
|
|
33
|
+
return {
|
|
34
|
+
startDate: end.minus({ days: 7 }).toISO() ?? "",
|
|
35
|
+
endDate: end.toISO() ?? to
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
if (thisWeek) {
|
|
39
|
+
const now2 = DateTime.now().toUTC();
|
|
40
|
+
return {
|
|
41
|
+
startDate: now2.startOf("week").toISO() ?? now2.minus({ days: 7 }).toISO() ?? "",
|
|
42
|
+
endDate: now2.endOf("week").toISO() ?? now2.toISO() ?? ""
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
const now = DateTime.now().toUTC();
|
|
46
|
+
return {
|
|
47
|
+
startDate: now.minus({ days: 7 }).toISO() ?? "",
|
|
48
|
+
endDate: now.toISO() ?? ""
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// src/commands/metrics/found.ts
|
|
53
|
+
async function runMetricsFound(options) {
|
|
54
|
+
const range = resolveDateRange({
|
|
55
|
+
from: options.from,
|
|
56
|
+
to: options.to,
|
|
57
|
+
thisWeek: options.thisWeek
|
|
58
|
+
});
|
|
59
|
+
const client = options.apiClient ?? createApiClient();
|
|
60
|
+
const result = await client.getMetricsFound({
|
|
61
|
+
slug: options.org,
|
|
62
|
+
startDate: range.startDate,
|
|
63
|
+
endDate: range.endDate,
|
|
64
|
+
brandIds: options.brandIds
|
|
65
|
+
});
|
|
66
|
+
const outputFormat = options.outputFormat ?? (options.json ? "json" : "human");
|
|
67
|
+
printOutput({
|
|
68
|
+
outputFormat,
|
|
69
|
+
json: result,
|
|
70
|
+
markdown: [
|
|
71
|
+
`# Found Threats (${options.org})`,
|
|
72
|
+
"",
|
|
73
|
+
`- Metric: ${result.metric}`,
|
|
74
|
+
`- Found: ${result.found}`,
|
|
75
|
+
`- Range: ${range.startDate} -> ${range.endDate}`
|
|
76
|
+
].join("\n"),
|
|
77
|
+
csv: toCsvRows([
|
|
78
|
+
{
|
|
79
|
+
metric: result.metric,
|
|
80
|
+
found: result.found,
|
|
81
|
+
startDate: range.startDate,
|
|
82
|
+
endDate: range.endDate
|
|
83
|
+
}
|
|
84
|
+
]),
|
|
85
|
+
human: () => {
|
|
86
|
+
console.log(`Found threats (${result.metric}) for ${options.org}: ${result.found}`);
|
|
87
|
+
console.log(`Range: ${range.startDate} -> ${range.endDate}`);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
export {
|
|
92
|
+
runMetricsFound
|
|
93
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CliExitError,
|
|
3
|
+
ExitCode
|
|
4
|
+
} from "./chunk-E2LAMILJ.js";
|
|
5
|
+
import {
|
|
6
|
+
printOutput,
|
|
7
|
+
toCsvRows
|
|
8
|
+
} from "./chunk-VFT3TD3E.js";
|
|
9
|
+
import {
|
|
10
|
+
createApiClient
|
|
11
|
+
} from "./chunk-H7UKKLCV.js";
|
|
12
|
+
import "./chunk-EEG7T6WT.js";
|
|
13
|
+
import "./chunk-U73SABXK.js";
|
|
14
|
+
|
|
15
|
+
// src/commands/detections/healthcheck.ts
|
|
16
|
+
async function runDetectionsHealthcheck(options) {
|
|
17
|
+
const client = options.apiClient ?? createApiClient();
|
|
18
|
+
const result = await client.validateDetectionConfigs({
|
|
19
|
+
slug: options.org,
|
|
20
|
+
source: options.source,
|
|
21
|
+
minResults: options.minResults,
|
|
22
|
+
lookbackHours: options.lookbackHours,
|
|
23
|
+
runBeforeValidate: options.run ?? false,
|
|
24
|
+
includeDisabled: options.includeDisabled ?? false
|
|
25
|
+
});
|
|
26
|
+
const outputFormat = options.outputFormat ?? (options.json ? "json" : "human");
|
|
27
|
+
const csv = toCsvRows(
|
|
28
|
+
result.validations.map((validation) => ({
|
|
29
|
+
configId: validation.configId,
|
|
30
|
+
source: validation.source,
|
|
31
|
+
status: validation.status,
|
|
32
|
+
valid: validation.valid,
|
|
33
|
+
ran: validation.ran,
|
|
34
|
+
runOk: validation.runOk,
|
|
35
|
+
recentResultCount: validation.recentResultCount,
|
|
36
|
+
runMessage: validation.runMessage
|
|
37
|
+
}))
|
|
38
|
+
);
|
|
39
|
+
const markdown = [
|
|
40
|
+
`# Detection Healthcheck (${options.org})`,
|
|
41
|
+
"",
|
|
42
|
+
`- Checked: ${result.summary.checkedConfigs}`,
|
|
43
|
+
`- Passing: ${result.summary.passingConfigs}`,
|
|
44
|
+
`- Failing: ${result.summary.failingConfigs}`,
|
|
45
|
+
`- Lookback hours: ${result.summary.lookbackHours}`,
|
|
46
|
+
`- Min results: ${result.summary.minResults}`,
|
|
47
|
+
"",
|
|
48
|
+
...result.validations.map((validation) => {
|
|
49
|
+
const runState = validation.ran ? validation.runOk ? "run-ok" : "run-failed" : "not-run";
|
|
50
|
+
return `- ${validation.valid ? "PASS" : "FAIL"} [${validation.source}] #${validation.configId} results=${validation.recentResultCount} ${runState}`;
|
|
51
|
+
})
|
|
52
|
+
].join("\n");
|
|
53
|
+
printOutput({
|
|
54
|
+
outputFormat,
|
|
55
|
+
json: {
|
|
56
|
+
...result,
|
|
57
|
+
explanation: options.explain ? {
|
|
58
|
+
checkType: "detection_healthcheck",
|
|
59
|
+
failureCondition: "result.ok === false"
|
|
60
|
+
} : void 0
|
|
61
|
+
},
|
|
62
|
+
markdown,
|
|
63
|
+
csv,
|
|
64
|
+
human: () => {
|
|
65
|
+
console.log(
|
|
66
|
+
`Checked ${result.summary.checkedConfigs} config(s), ${result.summary.passingConfigs} passing, ${result.summary.failingConfigs} failing`
|
|
67
|
+
);
|
|
68
|
+
console.log(
|
|
69
|
+
`Lookback: ${result.summary.lookbackHours}h, minimum results per config: ${result.summary.minResults}`
|
|
70
|
+
);
|
|
71
|
+
for (const validation of result.validations) {
|
|
72
|
+
const icon = validation.valid ? "\u2713" : "\u2717";
|
|
73
|
+
const runState = validation.ran ? validation.runOk ? "run-ok" : "run-failed" : "not-run";
|
|
74
|
+
console.log(
|
|
75
|
+
`${icon} [${validation.source}] config ${validation.configId} (${validation.status}) results=${validation.recentResultCount} ${runState}`
|
|
76
|
+
);
|
|
77
|
+
if (validation.runMessage) {
|
|
78
|
+
console.log(` ${validation.runMessage}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (options.explain) {
|
|
82
|
+
console.log(
|
|
83
|
+
"Healthcheck fails when any config is below min-results or run check fails."
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
if (!result.ok) {
|
|
89
|
+
throw new CliExitError("Detection healthcheck failed.", ExitCode.CHECK_FAILED);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
export {
|
|
93
|
+
runDetectionsHealthcheck
|
|
94
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CliExitError,
|
|
3
|
+
ExitCode
|
|
4
|
+
} from "./chunk-E2LAMILJ.js";
|
|
5
|
+
import {
|
|
6
|
+
printOutput,
|
|
7
|
+
toCsvRows
|
|
8
|
+
} from "./chunk-VFT3TD3E.js";
|
|
9
|
+
import {
|
|
10
|
+
createApiClient
|
|
11
|
+
} from "./chunk-H7UKKLCV.js";
|
|
12
|
+
import "./chunk-EEG7T6WT.js";
|
|
13
|
+
import "./chunk-U73SABXK.js";
|
|
14
|
+
|
|
15
|
+
// src/commands/reports/list.ts
|
|
16
|
+
function toReportRow(report) {
|
|
17
|
+
return {
|
|
18
|
+
id: report.id,
|
|
19
|
+
title: report.title,
|
|
20
|
+
status: report.status ?? "TODO",
|
|
21
|
+
createdAt: report.createdAt,
|
|
22
|
+
updatedAt: report.updatedAt,
|
|
23
|
+
assets: report.proposals.length,
|
|
24
|
+
reporter: report.externalReporter?.displayName ?? report.reporter?.fullName ?? report.externalReporter?.platform ?? "Unknown"
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
async function runReportsList(options) {
|
|
28
|
+
const outputFormat = options.outputFormat ?? (options.json ? "json" : "human");
|
|
29
|
+
const limit = options.limit ?? 10;
|
|
30
|
+
if (limit < 1 || limit > 20) {
|
|
31
|
+
throw new CliExitError(
|
|
32
|
+
"reports list requires --limit between 1 and 20.",
|
|
33
|
+
ExitCode.USAGE
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
const client = options.apiClient ?? createApiClient();
|
|
37
|
+
const result = await client.listOrganizationReports({
|
|
38
|
+
slug: options.org,
|
|
39
|
+
limit,
|
|
40
|
+
cursor: options.cursor,
|
|
41
|
+
status: options.status,
|
|
42
|
+
searchQuery: options.searchQuery
|
|
43
|
+
});
|
|
44
|
+
const rows = result.reports.map(toReportRow);
|
|
45
|
+
printOutput({
|
|
46
|
+
outputFormat,
|
|
47
|
+
json: {
|
|
48
|
+
...result,
|
|
49
|
+
explanation: options.explain ? {
|
|
50
|
+
listMode: "organization_reports",
|
|
51
|
+
filters: {
|
|
52
|
+
limit,
|
|
53
|
+
cursor: options.cursor,
|
|
54
|
+
status: options.status,
|
|
55
|
+
searchQuery: options.searchQuery
|
|
56
|
+
}
|
|
57
|
+
} : void 0
|
|
58
|
+
},
|
|
59
|
+
markdown: [
|
|
60
|
+
`# Reports (${options.org})`,
|
|
61
|
+
"",
|
|
62
|
+
`- Returned: ${result.reports.length}`,
|
|
63
|
+
`- Next cursor: ${result.nextCursor ?? "none"}`,
|
|
64
|
+
"",
|
|
65
|
+
...rows.map(
|
|
66
|
+
(row) => `- #${row.id} ${row.status} | ${row.title} | assets=${row.assets} | created=${row.createdAt}`
|
|
67
|
+
)
|
|
68
|
+
].join("\n"),
|
|
69
|
+
csv: toCsvRows(rows),
|
|
70
|
+
human: () => {
|
|
71
|
+
if (result.reports.length === 0) {
|
|
72
|
+
console.log("No reports found.");
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
console.log(`Reports for ${options.org} (returned ${result.reports.length})`);
|
|
76
|
+
for (const row of rows) {
|
|
77
|
+
console.log(
|
|
78
|
+
`#${row.id} [${row.status}] ${row.title} assets=${row.assets} created=${row.createdAt}`
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
if (result.nextCursor !== null) {
|
|
82
|
+
console.log(`Next cursor: ${result.nextCursor}`);
|
|
83
|
+
}
|
|
84
|
+
if (options.explain) {
|
|
85
|
+
console.log(
|
|
86
|
+
"Use --cursor <nextCursor> to paginate older reports and confirm creation history."
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
export {
|
|
93
|
+
runReportsList
|
|
94
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PRESETS
|
|
3
|
+
} from "./chunk-D2QGXYXZ.js";
|
|
4
|
+
import "./chunk-E2LAMILJ.js";
|
|
5
|
+
import {
|
|
6
|
+
printOutput,
|
|
7
|
+
toCsvRows
|
|
8
|
+
} from "./chunk-VFT3TD3E.js";
|
|
9
|
+
import "./chunk-H7UKKLCV.js";
|
|
10
|
+
import "./chunk-EEG7T6WT.js";
|
|
11
|
+
import "./chunk-U73SABXK.js";
|
|
12
|
+
|
|
13
|
+
// src/commands/presets/list.ts
|
|
14
|
+
async function runPresetsList({ outputFormat }) {
|
|
15
|
+
printOutput({
|
|
16
|
+
outputFormat,
|
|
17
|
+
json: { presets: PRESETS },
|
|
18
|
+
markdown: [
|
|
19
|
+
"# Available Presets",
|
|
20
|
+
"",
|
|
21
|
+
...PRESETS.map((preset) => `- \`${preset.id}\`: ${preset.description}`)
|
|
22
|
+
].join("\n"),
|
|
23
|
+
csv: toCsvRows(
|
|
24
|
+
PRESETS.map((preset) => ({
|
|
25
|
+
id: preset.id,
|
|
26
|
+
title: preset.title,
|
|
27
|
+
description: preset.description
|
|
28
|
+
}))
|
|
29
|
+
),
|
|
30
|
+
human: () => {
|
|
31
|
+
console.log("Available presets:");
|
|
32
|
+
for (const preset of PRESETS) {
|
|
33
|
+
console.log(`- ${preset.id}: ${preset.description}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
export {
|
|
39
|
+
runPresetsList
|
|
40
|
+
};
|