@chainpatrol/cli 0.12.0 → 0.13.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/CHANGELOG.md +27 -0
- package/dist/{breakdown-KCTKQNDY.js → breakdown-B6QUGYI6.js} +20 -5
- package/dist/{chunk-W42ITB5J.js → chunk-BQDMAEQU.js} +50 -5
- package/dist/chunk-S37VFIZY.js +66 -0
- package/dist/cli.js +23 -17
- package/dist/found-NXAWEIEK.js +59 -0
- package/dist/{organization-IIIOSTHF.js → organization-GS5LFIWO.js} +18 -5
- package/dist/{setup-skill-FA4STSGA.js → setup-skill-TPMTULHJ.js} +1 -1
- package/dist/{summary-Y2LG6UJM.js → summary-CV657J5V.js} +18 -5
- package/package.json +1 -1
- package/dist/found-EVXJZGYX.js +0 -95
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# @chainpatrol/cli
|
|
2
2
|
|
|
3
|
+
## 0.13.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 7cc2783: Default every `chainpatrol metrics …` subcommand
|
|
8
|
+
(`summary` / `found` / `breakdown` / `organization`) to a trailing
|
|
9
|
+
**3-month** window when no `--from`/`--to`/`--this-week` flag is
|
|
10
|
+
supplied, and echo the resolved range in every output format
|
|
11
|
+
(human, markdown, csv, json) so consumers can see exactly what
|
|
12
|
+
window the numbers describe.
|
|
13
|
+
|
|
14
|
+
Previously, omitting the date flags sent `undefined` for `startDate`
|
|
15
|
+
/ `endDate`, which caused the metrics handler to fan out ~15 parallel
|
|
16
|
+
unbounded Prisma aggregates per call. On large orgs that routinely
|
|
17
|
+
exceeded the platform's 30s function cap and surfaced to agents as
|
|
18
|
+
flaky `503` responses. The new default keeps the queries bounded
|
|
19
|
+
and produces a stable, labelled result.
|
|
20
|
+
|
|
21
|
+
`--this-week` is now also wired through to `metrics summary`,
|
|
22
|
+
`metrics breakdown`, and `metrics organization` (it was already
|
|
23
|
+
supported on `metrics found`).
|
|
24
|
+
|
|
25
|
+
JSON output now includes a `range: { startDate, endDate, label }`
|
|
26
|
+
field on every metrics subcommand. The label is one of
|
|
27
|
+
`"last 3 months"` (default), `"this week"`, or
|
|
28
|
+
`"custom range (<startIso> → <endIso>)"`.
|
|
29
|
+
|
|
3
30
|
## 0.12.0
|
|
4
31
|
|
|
5
32
|
### Minor Changes
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {
|
|
2
|
+
resolveDateRange
|
|
3
|
+
} from "./chunk-S37VFIZY.js";
|
|
1
4
|
import {
|
|
2
5
|
printOutput,
|
|
3
6
|
toCsvRows
|
|
@@ -11,21 +14,27 @@ import "./chunk-U73SABXK.js";
|
|
|
11
14
|
|
|
12
15
|
// src/commands/metrics/breakdown.ts
|
|
13
16
|
async function runMetricsBreakdown(options) {
|
|
17
|
+
const range = resolveDateRange({
|
|
18
|
+
from: options.from,
|
|
19
|
+
to: options.to,
|
|
20
|
+
thisWeek: options.thisWeek
|
|
21
|
+
});
|
|
14
22
|
const client = options.apiClient ?? createApiClient();
|
|
15
23
|
const result = await client.getMetricsBreakdown({
|
|
16
24
|
slug: options.org,
|
|
17
25
|
by: options.by,
|
|
18
|
-
startDate:
|
|
19
|
-
endDate:
|
|
26
|
+
startDate: range.startDate,
|
|
27
|
+
endDate: range.endDate,
|
|
20
28
|
brandIds: options.brandIds
|
|
21
29
|
});
|
|
22
30
|
const outputFormat = options.outputFormat ?? (options.json ? "json" : "human");
|
|
23
31
|
printOutput({
|
|
24
32
|
outputFormat,
|
|
25
|
-
json: result,
|
|
33
|
+
json: { ...result, range },
|
|
26
34
|
markdown: [
|
|
27
|
-
`# Metrics Breakdown (${options.org})`,
|
|
35
|
+
`# Metrics Breakdown (${options.org}) \u2014 ${range.label}`,
|
|
28
36
|
"",
|
|
37
|
+
`- Range: ${range.startDate} \u2192 ${range.endDate}`,
|
|
29
38
|
`- Grouped by: ${result.by}`,
|
|
30
39
|
"",
|
|
31
40
|
...result.points.map((point) => {
|
|
@@ -40,6 +49,9 @@ async function runMetricsBreakdown(options) {
|
|
|
40
49
|
].join("\n"),
|
|
41
50
|
csv: toCsvRows(
|
|
42
51
|
result.points.map((point) => ({
|
|
52
|
+
rangeLabel: range.label,
|
|
53
|
+
startDate: range.startDate,
|
|
54
|
+
endDate: range.endDate,
|
|
43
55
|
key: point.key,
|
|
44
56
|
count: point.count,
|
|
45
57
|
date: point.date ?? null,
|
|
@@ -50,7 +62,10 @@ async function runMetricsBreakdown(options) {
|
|
|
50
62
|
}))
|
|
51
63
|
),
|
|
52
64
|
human: () => {
|
|
53
|
-
console.log(
|
|
65
|
+
console.log(
|
|
66
|
+
`Metrics breakdown for ${options.org} by ${result.by} \u2014 ${range.label}`
|
|
67
|
+
);
|
|
68
|
+
console.log(`Range: ${range.startDate} \u2192 ${range.endDate}`);
|
|
54
69
|
for (const point of result.points) {
|
|
55
70
|
if (result.by === "day") {
|
|
56
71
|
console.log(`${point.date ?? point.key}: ${point.count}`);
|
|
@@ -659,20 +659,65 @@ takedowns?", reach for \`orgs list\` \u2014 it's the only command that exposes
|
|
|
659
659
|
service flags across multiple orgs in one call. Run it in \`--json\` mode
|
|
660
660
|
and summarize patterns by service or by subscription tier.
|
|
661
661
|
|
|
662
|
-
### \`metrics summary | found | breakdown\` \u2014 Org metrics for spike/drop analysis
|
|
662
|
+
### \`metrics summary | found | breakdown | organization\` \u2014 Org metrics for spike/drop analysis
|
|
663
663
|
|
|
664
664
|
\`\`\`bash
|
|
665
|
-
chainpatrol --json metrics summary
|
|
666
|
-
chainpatrol --json metrics
|
|
667
|
-
chainpatrol --json metrics
|
|
665
|
+
chainpatrol --json metrics summary --org <slug> # defaults to last 3 months
|
|
666
|
+
chainpatrol --json metrics summary --org <slug> --this-week
|
|
667
|
+
chainpatrol --json metrics breakdown --org <slug> --by day --this-week
|
|
668
|
+
chainpatrol --json metrics found --org <slug> --from <YYYY-MM-DD> --to <YYYY-MM-DD>
|
|
669
|
+
chainpatrol --json metrics organization --org <slug> # defaults to last 3 months
|
|
668
670
|
\`\`\`
|
|
669
671
|
|
|
672
|
+
**Always operate on a bounded window.** When no \`--from\`/\`--to\`/\`--this-week\`
|
|
673
|
+
is passed, every metrics subcommand defaults to the trailing **last 3 months**
|
|
674
|
+
ending now. The default exists because unbounded org-wide aggregates over
|
|
675
|
+
multi-year history routinely time out at the platform layer (Vercel's
|
|
676
|
+
30s function cap), which surfaces to clients as a 503. Stick to the default,
|
|
677
|
+
or pass an explicit narrower window \u2014 don't try to bypass the default by
|
|
678
|
+
guessing wide \`--from\` values.
|
|
679
|
+
|
|
680
|
+
The resolved range is echoed back in every output format so the user can
|
|
681
|
+
see exactly what they got:
|
|
682
|
+
|
|
683
|
+
- JSON: \`{ "range": { "startDate": "...", "endDate": "...", "label": "last 3 months" } }\`
|
|
684
|
+
- Markdown / human: the header line includes the label, e.g.
|
|
685
|
+
\`Organization metrics for acme \u2014 last 3 months\`, followed by
|
|
686
|
+
\`Range: <startDate> \u2192 <endDate>\`.
|
|
687
|
+
|
|
688
|
+
When reporting numbers to the user (especially averages and rates),
|
|
689
|
+
**state the window explicitly** \u2014 say "averaged over the last 3 months"
|
|
690
|
+
rather than just quoting a number, since the same prompt phrased
|
|
691
|
+
differently can pick a different window.
|
|
692
|
+
|
|
670
693
|
\`breakdown\` is the one you usually want for healthchecks: it returns a
|
|
671
694
|
time series (by day or week) of reports, new threats, watchlisted threats,
|
|
672
695
|
and takedowns filed/completed. Compare the latest period against a prior
|
|
673
696
|
window to spot the **spike** or **drop** signals described in the manual
|
|
674
697
|
HealthCheck Guide. \`summary\` returns a single window total; \`found\` is
|
|
675
|
-
oriented around when threats were first discovered
|
|
698
|
+
oriented around when threats were first discovered; \`organization\` is
|
|
699
|
+
the full customer-facing dashboard slice (reports, new threats,
|
|
700
|
+
watchlisted, takedowns filed/completed, plus per-type and per-day
|
|
701
|
+
breakdowns).
|
|
702
|
+
|
|
703
|
+
#### "Across all orgs" via an API key \u2014 what's actually possible
|
|
704
|
+
|
|
705
|
+
A ChainPatrol API key is scoped to a single organization. The
|
|
706
|
+
\`/organization/metrics\` endpoint derives the org from the key, so
|
|
707
|
+
**there is no way to fetch metrics for multiple orgs with one API key**.
|
|
708
|
+
If the user asks for "averages across all orgs":
|
|
709
|
+
|
|
710
|
+
1. If they're authenticated with a user session, list accessible orgs
|
|
711
|
+
first (\`chainpatrol --json orgs list\`) and then call
|
|
712
|
+
\`metrics organization\` once per org **serially** (concurrent
|
|
713
|
+
fan-out across many orgs is what triggers 503/throttling on the
|
|
714
|
+
metrics endpoint).
|
|
715
|
+
2. If they're using a single API key, explain that the key only
|
|
716
|
+
covers one org \u2014 they need either session auth or one key per
|
|
717
|
+
org, not a loop with the same key.
|
|
718
|
+
3. Whatever window you choose, **make it explicit** in your reply
|
|
719
|
+
("last 3 months", "last 7 days", etc) \u2014 never report an average
|
|
720
|
+
without naming the window it was computed over.
|
|
676
721
|
|
|
677
722
|
### \`presets list | run\` \u2014 Packaged workflows for common jobs
|
|
678
723
|
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DateTime
|
|
3
|
+
} from "./chunk-TFCNKBRC.js";
|
|
4
|
+
|
|
5
|
+
// src/lib/date-range.ts
|
|
6
|
+
var DEFAULT_RANGE_MONTHS = 3;
|
|
7
|
+
var DEFAULT_RANGE_LABEL = `last ${DEFAULT_RANGE_MONTHS} months`;
|
|
8
|
+
function customLabel(start, end) {
|
|
9
|
+
const startIso = start.toUTC().toISODate();
|
|
10
|
+
const endIso = end.toUTC().toISODate();
|
|
11
|
+
return `custom range (${startIso} \u2192 ${endIso})`;
|
|
12
|
+
}
|
|
13
|
+
function resolveDateRange({
|
|
14
|
+
from,
|
|
15
|
+
to,
|
|
16
|
+
thisWeek
|
|
17
|
+
}) {
|
|
18
|
+
if (from && to) {
|
|
19
|
+
const start2 = DateTime.fromISO(from).toUTC();
|
|
20
|
+
const end = DateTime.fromISO(to).toUTC();
|
|
21
|
+
return {
|
|
22
|
+
startDate: start2.toISO() ?? from,
|
|
23
|
+
endDate: end.toISO() ?? to,
|
|
24
|
+
label: customLabel(start2, end)
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
if (from && !to) {
|
|
28
|
+
const now2 = DateTime.now().toUTC();
|
|
29
|
+
const start2 = DateTime.fromISO(from).toUTC();
|
|
30
|
+
return {
|
|
31
|
+
startDate: start2.toISO() ?? from,
|
|
32
|
+
endDate: now2.toISO() ?? "",
|
|
33
|
+
label: customLabel(start2, now2)
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
if (!from && to) {
|
|
37
|
+
const end = DateTime.fromISO(to).toUTC();
|
|
38
|
+
const start2 = end.minus({ days: 7 });
|
|
39
|
+
return {
|
|
40
|
+
startDate: start2.toISO() ?? "",
|
|
41
|
+
endDate: end.toISO() ?? to,
|
|
42
|
+
label: customLabel(start2, end)
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
if (thisWeek) {
|
|
46
|
+
const now2 = DateTime.now().toUTC();
|
|
47
|
+
const start2 = now2.startOf("week");
|
|
48
|
+
const end = now2.endOf("week");
|
|
49
|
+
return {
|
|
50
|
+
startDate: start2.toISO() ?? now2.minus({ days: 7 }).toISO() ?? "",
|
|
51
|
+
endDate: end.toISO() ?? now2.toISO() ?? "",
|
|
52
|
+
label: "this week"
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
const now = DateTime.now().toUTC();
|
|
56
|
+
const start = now.minus({ months: DEFAULT_RANGE_MONTHS });
|
|
57
|
+
return {
|
|
58
|
+
startDate: start.toISO() ?? "",
|
|
59
|
+
endDate: now.toISO() ?? "",
|
|
60
|
+
label: DEFAULT_RANGE_LABEL
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export {
|
|
65
|
+
resolveDateRange
|
|
66
|
+
};
|
package/dist/cli.js
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
getCliVersion,
|
|
14
14
|
isSkillInstalled,
|
|
15
15
|
readInstalledSkillVersion
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-BQDMAEQU.js";
|
|
17
17
|
import "./chunk-NQT77IB7.js";
|
|
18
18
|
import {
|
|
19
19
|
DateTime
|
|
@@ -263,49 +263,52 @@ var HELP = {
|
|
|
263
263
|
]
|
|
264
264
|
},
|
|
265
265
|
"metrics organization": {
|
|
266
|
-
description: "Get organization metrics via the documented GET /organization/metrics endpoint. Works with both API key auth (org derived from key) and session auth (pass --org).",
|
|
267
|
-
usage: "chainpatrol metrics organization [--org <slug>] [--from ISO --to ISO]",
|
|
266
|
+
description: "Get organization metrics via the documented GET /organization/metrics endpoint. Works with both API key auth (org derived from key) and session auth (pass --org). Defaults to the trailing 3 months if no date range is given.",
|
|
267
|
+
usage: "chainpatrol metrics organization [--org <slug>] [--from ISO --to ISO|--this-week]",
|
|
268
268
|
options: [
|
|
269
269
|
"--org <slug> Organization slug (omit when using an API key)",
|
|
270
270
|
"--brand-slug <slug> Filter by brand slug (requires brands-metrics flag)",
|
|
271
271
|
"--from <iso> Start date (ISO 8601, e.g. 2026-01-01)",
|
|
272
|
-
"--to <iso> End date (ISO 8601)"
|
|
272
|
+
"--to <iso> End date (ISO 8601)",
|
|
273
|
+
"--this-week Use the current ISO week (overrides default)"
|
|
273
274
|
],
|
|
274
275
|
examples: [
|
|
275
|
-
"chainpatrol metrics organization --org acme",
|
|
276
|
+
"chainpatrol metrics organization --org acme # defaults to last 3 months",
|
|
276
277
|
"chainpatrol metrics organization --org acme --from 2026-01-01 --to 2026-02-01",
|
|
277
278
|
"CHAINPATROL_API_KEY=... chainpatrol metrics organization --json"
|
|
278
279
|
]
|
|
279
280
|
},
|
|
280
281
|
"metrics summary": {
|
|
281
|
-
description: "Summary metrics for an org over a date range.",
|
|
282
|
-
usage: "chainpatrol metrics summary --org <slug> [--from ISO --to ISO]",
|
|
282
|
+
description: "Summary metrics for an org over a date range. Defaults to the trailing 3 months if no date range is given.",
|
|
283
|
+
usage: "chainpatrol metrics summary --org <slug> [--from ISO --to ISO|--this-week]",
|
|
283
284
|
options: [
|
|
284
285
|
"--org <slug> Organization slug",
|
|
285
286
|
"--from <iso> Start date (ISO 8601)",
|
|
286
287
|
"--to <iso> End date (ISO 8601)",
|
|
288
|
+
"--this-week Use the current ISO week (overrides default)",
|
|
287
289
|
"--brand <ids> Comma-separated brand IDs"
|
|
288
290
|
]
|
|
289
291
|
},
|
|
290
292
|
"metrics found": {
|
|
291
|
-
description: "Count of newly found threats over a date range.",
|
|
293
|
+
description: "Count of newly found threats over a date range. Defaults to the trailing 3 months if no date range is given.",
|
|
292
294
|
usage: "chainpatrol metrics found --org <slug> [--from ISO --to ISO|--this-week]",
|
|
293
295
|
options: [
|
|
294
296
|
"--org <slug> Organization slug",
|
|
295
297
|
"--from <iso> Start date (ISO 8601)",
|
|
296
298
|
"--to <iso> End date (ISO 8601)",
|
|
297
|
-
"--this-week Use the current week",
|
|
299
|
+
"--this-week Use the current ISO week (overrides default)",
|
|
298
300
|
"--brand <ids> Comma-separated brand IDs"
|
|
299
301
|
]
|
|
300
302
|
},
|
|
301
303
|
"metrics breakdown": {
|
|
302
|
-
description: "Threat counts grouped by axis (day, type, brand).",
|
|
303
|
-
usage: "chainpatrol metrics breakdown --org <slug> --by <day|type|brand>",
|
|
304
|
+
description: "Threat counts grouped by axis (day, type, brand). Defaults to the trailing 3 months if no date range is given.",
|
|
305
|
+
usage: "chainpatrol metrics breakdown --org <slug> --by <day|type|brand> [--from ISO --to ISO|--this-week]",
|
|
304
306
|
options: [
|
|
305
307
|
"--org <slug> Organization slug",
|
|
306
308
|
"--by <axis> day|type|brand (required)",
|
|
307
309
|
"--from <iso> Start date",
|
|
308
310
|
"--to <iso> End date",
|
|
311
|
+
"--this-week Use the current ISO week (overrides default)",
|
|
309
312
|
"--brand <ids> Comma-separated brand IDs"
|
|
310
313
|
]
|
|
311
314
|
},
|
|
@@ -1303,12 +1306,13 @@ async function main() {
|
|
|
1303
1306
|
case "metrics": {
|
|
1304
1307
|
if (subcommand === "organization" || subcommand === "org") {
|
|
1305
1308
|
const org2 = await tryResolveOrg();
|
|
1306
|
-
const { runMetricsOrganization } = await import("./organization-
|
|
1309
|
+
const { runMetricsOrganization } = await import("./organization-GS5LFIWO.js");
|
|
1307
1310
|
await runMetricsOrganization({
|
|
1308
1311
|
org: org2,
|
|
1309
1312
|
brandSlug: cli.flags.brandSlug,
|
|
1310
1313
|
from: cli.flags.from,
|
|
1311
1314
|
to: cli.flags.to,
|
|
1315
|
+
thisWeek: cli.flags.thisWeek,
|
|
1312
1316
|
json: jsonMode,
|
|
1313
1317
|
outputFormat: cliContext.outputFormat
|
|
1314
1318
|
});
|
|
@@ -1316,11 +1320,12 @@ async function main() {
|
|
|
1316
1320
|
}
|
|
1317
1321
|
const org = await resolveOrg();
|
|
1318
1322
|
if (subcommand === "summary") {
|
|
1319
|
-
const { runMetricsSummary } = await import("./summary-
|
|
1323
|
+
const { runMetricsSummary } = await import("./summary-CV657J5V.js");
|
|
1320
1324
|
await runMetricsSummary({
|
|
1321
1325
|
org,
|
|
1322
1326
|
from: cli.flags.from,
|
|
1323
1327
|
to: cli.flags.to,
|
|
1328
|
+
thisWeek: cli.flags.thisWeek,
|
|
1324
1329
|
brandIds: parseBrandIds(),
|
|
1325
1330
|
json: jsonMode,
|
|
1326
1331
|
outputFormat: cliContext.outputFormat
|
|
@@ -1328,7 +1333,7 @@ async function main() {
|
|
|
1328
1333
|
break;
|
|
1329
1334
|
}
|
|
1330
1335
|
if (subcommand === "found") {
|
|
1331
|
-
const { runMetricsFound } = await import("./found-
|
|
1336
|
+
const { runMetricsFound } = await import("./found-NXAWEIEK.js");
|
|
1332
1337
|
await runMetricsFound({
|
|
1333
1338
|
org,
|
|
1334
1339
|
from: cli.flags.from,
|
|
@@ -1345,12 +1350,13 @@ async function main() {
|
|
|
1345
1350
|
if (!by || !["day", "type", "brand"].includes(by)) {
|
|
1346
1351
|
throw new Error("metrics breakdown requires --by <day|type|brand>");
|
|
1347
1352
|
}
|
|
1348
|
-
const { runMetricsBreakdown } = await import("./breakdown-
|
|
1353
|
+
const { runMetricsBreakdown } = await import("./breakdown-B6QUGYI6.js");
|
|
1349
1354
|
await runMetricsBreakdown({
|
|
1350
1355
|
org,
|
|
1351
1356
|
by,
|
|
1352
1357
|
from: cli.flags.from,
|
|
1353
1358
|
to: cli.flags.to,
|
|
1359
|
+
thisWeek: cli.flags.thisWeek,
|
|
1354
1360
|
brandIds: parseBrandIds(),
|
|
1355
1361
|
json: jsonMode,
|
|
1356
1362
|
outputFormat: cliContext.outputFormat
|
|
@@ -1617,12 +1623,12 @@ async function main() {
|
|
|
1617
1623
|
case "setup":
|
|
1618
1624
|
case "install":
|
|
1619
1625
|
case "i": {
|
|
1620
|
-
const { setupSkill } = await import("./setup-skill-
|
|
1626
|
+
const { setupSkill } = await import("./setup-skill-TPMTULHJ.js");
|
|
1621
1627
|
setupSkill({ json: jsonMode, cloud: cli.flags.cloud });
|
|
1622
1628
|
break;
|
|
1623
1629
|
}
|
|
1624
1630
|
case "uninstall": {
|
|
1625
|
-
const { uninstallSkill } = await import("./setup-skill-
|
|
1631
|
+
const { uninstallSkill } = await import("./setup-skill-TPMTULHJ.js");
|
|
1626
1632
|
uninstallSkill({ json: jsonMode });
|
|
1627
1633
|
break;
|
|
1628
1634
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import {
|
|
2
|
+
resolveDateRange
|
|
3
|
+
} from "./chunk-S37VFIZY.js";
|
|
4
|
+
import {
|
|
5
|
+
printOutput,
|
|
6
|
+
toCsvRows
|
|
7
|
+
} from "./chunk-VFT3TD3E.js";
|
|
8
|
+
import {
|
|
9
|
+
createApiClient
|
|
10
|
+
} from "./chunk-OSRFCVBT.js";
|
|
11
|
+
import "./chunk-EGWK6SRQ.js";
|
|
12
|
+
import "./chunk-TFCNKBRC.js";
|
|
13
|
+
import "./chunk-U73SABXK.js";
|
|
14
|
+
|
|
15
|
+
// src/commands/metrics/found.ts
|
|
16
|
+
async function runMetricsFound(options) {
|
|
17
|
+
const range = resolveDateRange({
|
|
18
|
+
from: options.from,
|
|
19
|
+
to: options.to,
|
|
20
|
+
thisWeek: options.thisWeek
|
|
21
|
+
});
|
|
22
|
+
const client = options.apiClient ?? createApiClient();
|
|
23
|
+
const result = await client.getMetricsFound({
|
|
24
|
+
slug: options.org,
|
|
25
|
+
startDate: range.startDate,
|
|
26
|
+
endDate: range.endDate,
|
|
27
|
+
brandIds: options.brandIds
|
|
28
|
+
});
|
|
29
|
+
const outputFormat = options.outputFormat ?? (options.json ? "json" : "human");
|
|
30
|
+
printOutput({
|
|
31
|
+
outputFormat,
|
|
32
|
+
json: { ...result, range },
|
|
33
|
+
markdown: [
|
|
34
|
+
`# Found Threats (${options.org}) \u2014 ${range.label}`,
|
|
35
|
+
"",
|
|
36
|
+
`- Metric: ${result.metric}`,
|
|
37
|
+
`- Found: ${result.found}`,
|
|
38
|
+
`- Range: ${range.startDate} \u2192 ${range.endDate}`
|
|
39
|
+
].join("\n"),
|
|
40
|
+
csv: toCsvRows([
|
|
41
|
+
{
|
|
42
|
+
rangeLabel: range.label,
|
|
43
|
+
metric: result.metric,
|
|
44
|
+
found: result.found,
|
|
45
|
+
startDate: range.startDate,
|
|
46
|
+
endDate: range.endDate
|
|
47
|
+
}
|
|
48
|
+
]),
|
|
49
|
+
human: () => {
|
|
50
|
+
console.log(
|
|
51
|
+
`Found threats (${result.metric}) for ${options.org} \u2014 ${range.label}: ${result.found}`
|
|
52
|
+
);
|
|
53
|
+
console.log(`Range: ${range.startDate} \u2192 ${range.endDate}`);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
export {
|
|
58
|
+
runMetricsFound
|
|
59
|
+
};
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {
|
|
2
|
+
resolveDateRange
|
|
3
|
+
} from "./chunk-S37VFIZY.js";
|
|
1
4
|
import {
|
|
2
5
|
printOutput,
|
|
3
6
|
toCsvRows
|
|
@@ -13,20 +16,26 @@ import "./chunk-U73SABXK.js";
|
|
|
13
16
|
async function runMetricsOrganization(options) {
|
|
14
17
|
const outputFormat = options.outputFormat ?? (options.json ? "json" : "human");
|
|
15
18
|
const client = options.apiClient ?? createApiClient();
|
|
19
|
+
const range = resolveDateRange({
|
|
20
|
+
from: options.from,
|
|
21
|
+
to: options.to,
|
|
22
|
+
thisWeek: options.thisWeek
|
|
23
|
+
});
|
|
16
24
|
const input = {
|
|
17
25
|
organizationSlug: options.org,
|
|
18
26
|
brandSlug: options.brandSlug,
|
|
19
|
-
startDate:
|
|
20
|
-
endDate:
|
|
27
|
+
startDate: range.startDate,
|
|
28
|
+
endDate: range.endDate
|
|
21
29
|
};
|
|
22
30
|
const result = await client.getOrganizationMetrics(input);
|
|
23
31
|
const label = options.org ?? "your organization";
|
|
24
32
|
printOutput({
|
|
25
33
|
outputFormat,
|
|
26
|
-
json: result,
|
|
34
|
+
json: { ...result, range },
|
|
27
35
|
markdown: [
|
|
28
|
-
`# Organization Metrics (${label})`,
|
|
36
|
+
`# Organization Metrics (${label}) \u2014 ${range.label}`,
|
|
29
37
|
"",
|
|
38
|
+
`- Range: ${range.startDate} \u2192 ${range.endDate}`,
|
|
30
39
|
`- Reports: ${result.metrics.reports}`,
|
|
31
40
|
`- New threats: ${result.metrics.newThreats}`,
|
|
32
41
|
`- Watchlisted threats: ${result.metrics.threatsWatchlisted}`,
|
|
@@ -39,6 +48,9 @@ async function runMetricsOrganization(options) {
|
|
|
39
48
|
].join("\n"),
|
|
40
49
|
csv: toCsvRows([
|
|
41
50
|
{
|
|
51
|
+
rangeLabel: range.label,
|
|
52
|
+
startDate: range.startDate,
|
|
53
|
+
endDate: range.endDate,
|
|
42
54
|
reports: result.metrics.reports,
|
|
43
55
|
newThreats: result.metrics.newThreats,
|
|
44
56
|
threatsWatchlisted: result.metrics.threatsWatchlisted,
|
|
@@ -51,7 +63,8 @@ async function runMetricsOrganization(options) {
|
|
|
51
63
|
}
|
|
52
64
|
]),
|
|
53
65
|
human: () => {
|
|
54
|
-
console.log(`Organization metrics for ${label}`);
|
|
66
|
+
console.log(`Organization metrics for ${label} \u2014 ${range.label}`);
|
|
67
|
+
console.log(`Range: ${range.startDate} \u2192 ${range.endDate}`);
|
|
55
68
|
console.log(`Reports: ${result.metrics.reports}`);
|
|
56
69
|
console.log(`New threats: ${result.metrics.newThreats}`);
|
|
57
70
|
console.log(`Watchlisted threats: ${result.metrics.threatsWatchlisted}`);
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {
|
|
2
|
+
resolveDateRange
|
|
3
|
+
} from "./chunk-S37VFIZY.js";
|
|
1
4
|
import {
|
|
2
5
|
printOutput,
|
|
3
6
|
toCsvRows
|
|
@@ -11,20 +14,26 @@ import "./chunk-U73SABXK.js";
|
|
|
11
14
|
|
|
12
15
|
// src/commands/metrics/summary.ts
|
|
13
16
|
async function runMetricsSummary(options) {
|
|
17
|
+
const range = resolveDateRange({
|
|
18
|
+
from: options.from,
|
|
19
|
+
to: options.to,
|
|
20
|
+
thisWeek: options.thisWeek
|
|
21
|
+
});
|
|
14
22
|
const client = options.apiClient ?? createApiClient();
|
|
15
23
|
const result = await client.getMetricsSummary({
|
|
16
24
|
slug: options.org,
|
|
17
|
-
startDate:
|
|
18
|
-
endDate:
|
|
25
|
+
startDate: range.startDate,
|
|
26
|
+
endDate: range.endDate,
|
|
19
27
|
brandIds: options.brandIds
|
|
20
28
|
});
|
|
21
29
|
const outputFormat = options.outputFormat ?? (options.json ? "json" : "human");
|
|
22
30
|
printOutput({
|
|
23
31
|
outputFormat,
|
|
24
|
-
json: result,
|
|
32
|
+
json: { ...result, range },
|
|
25
33
|
markdown: [
|
|
26
|
-
`# Metrics Summary (${options.org})`,
|
|
34
|
+
`# Metrics Summary (${options.org}) \u2014 ${range.label}`,
|
|
27
35
|
"",
|
|
36
|
+
`- Range: ${range.startDate} \u2192 ${range.endDate}`,
|
|
28
37
|
`- Reports: ${result.metrics.reports}`,
|
|
29
38
|
`- New threats: ${result.metrics.newThreats}`,
|
|
30
39
|
`- Watchlisted threats: ${result.metrics.threatsWatchlisted}`,
|
|
@@ -33,6 +42,9 @@ async function runMetricsSummary(options) {
|
|
|
33
42
|
].join("\n"),
|
|
34
43
|
csv: toCsvRows([
|
|
35
44
|
{
|
|
45
|
+
rangeLabel: range.label,
|
|
46
|
+
startDate: range.startDate,
|
|
47
|
+
endDate: range.endDate,
|
|
36
48
|
reports: result.metrics.reports,
|
|
37
49
|
newThreats: result.metrics.newThreats,
|
|
38
50
|
threatsWatchlisted: result.metrics.threatsWatchlisted,
|
|
@@ -45,7 +57,8 @@ async function runMetricsSummary(options) {
|
|
|
45
57
|
}
|
|
46
58
|
]),
|
|
47
59
|
human: () => {
|
|
48
|
-
console.log(`Metrics summary for ${options.org}`);
|
|
60
|
+
console.log(`Metrics summary for ${options.org} \u2014 ${range.label}`);
|
|
61
|
+
console.log(`Range: ${range.startDate} \u2192 ${range.endDate}`);
|
|
49
62
|
console.log(`Reports: ${result.metrics.reports}`);
|
|
50
63
|
console.log(`New threats: ${result.metrics.newThreats}`);
|
|
51
64
|
console.log(`Watchlisted threats: ${result.metrics.threatsWatchlisted}`);
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@chainpatrol/cli",
|
|
3
3
|
"description": "The official ChainPatrol CLI — terminal interface for threat detection",
|
|
4
4
|
"author": "Umar Ahmed <umar@chainpatrol.io>",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.13.0",
|
|
6
6
|
"license": "UNLICENSED",
|
|
7
7
|
"homepage": "https://chainpatrol.com/docs/cli",
|
|
8
8
|
"keywords": [
|
package/dist/found-EVXJZGYX.js
DELETED
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
printOutput,
|
|
3
|
-
toCsvRows
|
|
4
|
-
} from "./chunk-VFT3TD3E.js";
|
|
5
|
-
import {
|
|
6
|
-
createApiClient
|
|
7
|
-
} from "./chunk-OSRFCVBT.js";
|
|
8
|
-
import "./chunk-EGWK6SRQ.js";
|
|
9
|
-
import {
|
|
10
|
-
DateTime
|
|
11
|
-
} from "./chunk-TFCNKBRC.js";
|
|
12
|
-
import "./chunk-U73SABXK.js";
|
|
13
|
-
|
|
14
|
-
// src/lib/date-range.ts
|
|
15
|
-
function resolveDateRange({
|
|
16
|
-
from,
|
|
17
|
-
to,
|
|
18
|
-
thisWeek
|
|
19
|
-
}) {
|
|
20
|
-
if (from && to) {
|
|
21
|
-
return {
|
|
22
|
-
startDate: DateTime.fromISO(from).toUTC().toISO() ?? from,
|
|
23
|
-
endDate: DateTime.fromISO(to).toUTC().toISO() ?? to
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
if (from && !to) {
|
|
27
|
-
const now2 = DateTime.now().toUTC();
|
|
28
|
-
return {
|
|
29
|
-
startDate: DateTime.fromISO(from).toUTC().toISO() ?? from,
|
|
30
|
-
endDate: now2.toISO() ?? ""
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
if (!from && to) {
|
|
34
|
-
const end = DateTime.fromISO(to).toUTC();
|
|
35
|
-
return {
|
|
36
|
-
startDate: end.minus({ days: 7 }).toISO() ?? "",
|
|
37
|
-
endDate: end.toISO() ?? to
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
if (thisWeek) {
|
|
41
|
-
const now2 = DateTime.now().toUTC();
|
|
42
|
-
return {
|
|
43
|
-
startDate: now2.startOf("week").toISO() ?? now2.minus({ days: 7 }).toISO() ?? "",
|
|
44
|
-
endDate: now2.endOf("week").toISO() ?? now2.toISO() ?? ""
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
const now = DateTime.now().toUTC();
|
|
48
|
-
return {
|
|
49
|
-
startDate: now.minus({ days: 7 }).toISO() ?? "",
|
|
50
|
-
endDate: now.toISO() ?? ""
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// src/commands/metrics/found.ts
|
|
55
|
-
async function runMetricsFound(options) {
|
|
56
|
-
const range = resolveDateRange({
|
|
57
|
-
from: options.from,
|
|
58
|
-
to: options.to,
|
|
59
|
-
thisWeek: options.thisWeek
|
|
60
|
-
});
|
|
61
|
-
const client = options.apiClient ?? createApiClient();
|
|
62
|
-
const result = await client.getMetricsFound({
|
|
63
|
-
slug: options.org,
|
|
64
|
-
startDate: range.startDate,
|
|
65
|
-
endDate: range.endDate,
|
|
66
|
-
brandIds: options.brandIds
|
|
67
|
-
});
|
|
68
|
-
const outputFormat = options.outputFormat ?? (options.json ? "json" : "human");
|
|
69
|
-
printOutput({
|
|
70
|
-
outputFormat,
|
|
71
|
-
json: result,
|
|
72
|
-
markdown: [
|
|
73
|
-
`# Found Threats (${options.org})`,
|
|
74
|
-
"",
|
|
75
|
-
`- Metric: ${result.metric}`,
|
|
76
|
-
`- Found: ${result.found}`,
|
|
77
|
-
`- Range: ${range.startDate} -> ${range.endDate}`
|
|
78
|
-
].join("\n"),
|
|
79
|
-
csv: toCsvRows([
|
|
80
|
-
{
|
|
81
|
-
metric: result.metric,
|
|
82
|
-
found: result.found,
|
|
83
|
-
startDate: range.startDate,
|
|
84
|
-
endDate: range.endDate
|
|
85
|
-
}
|
|
86
|
-
]),
|
|
87
|
-
human: () => {
|
|
88
|
-
console.log(`Found threats (${result.metric}) for ${options.org}: ${result.found}`);
|
|
89
|
-
console.log(`Range: ${range.startDate} -> ${range.endDate}`);
|
|
90
|
-
}
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
export {
|
|
94
|
-
runMetricsFound
|
|
95
|
-
};
|